/Tutorial/example_html/src/krona-2.0.js
JavaScript | 6290 lines | 4980 code | 851 blank | 459 comment | 843 complexity | 11f3a823b037886b4ed607b63ffa4f00 MD5 | raw file
Possible License(s): GPL-2.0
Large files files are truncated, but you can click here to view the full file
1{//----------------------------------------------------------------------------- 2// 3// PURPOSE 4// 5// Krona is a flexible tool for exploring the relative proportions of 6// hierarchical data, such as metagenomic classifications, using a 7// radial, space-filling display. It is implemented using HTML5 and 8// JavaScript, allowing charts to be explored locally or served over the 9// Internet, requiring only a current version of any major web 10// browser. Krona charts can be created using an Excel template or from 11// common bioinformatic formats using the provided conversion scripts. 12// 13// 14// COPYRIGHT LICENSE 15// 16// Copyright (c) 2011, Battelle National Biodefense Institute (BNBI); 17// all rights reserved. Authored by: Brian Ondov, Nicholas Bergman, and 18// Adam Phillippy 19// 20// This Software was prepared for the Department of Homeland Security 21// (DHS) by the Battelle National Biodefense Institute, LLC (BNBI) as 22// part of contract HSHQDC-07-C-00020 to manage and operate the National 23// Biodefense Analysis and Countermeasures Center (NBACC), a Federally 24// Funded Research and Development Center. 25// 26// Redistribution and use in source and binary forms, with or without 27// modification, are permitted provided that the following conditions are 28// met: 29// 30// * Redistributions of source code must retain the above copyright 31// notice, this list of conditions and the following disclaimer. 32// 33// * Redistributions in binary form must reproduce the above copyright 34// notice, this list of conditions and the following disclaimer in the 35// documentation and/or other materials provided with the distribution. 36// 37// * Neither the name of the Battelle National Biodefense Institute nor 38// the names of its contributors may be used to endorse or promote 39// products derived from this software without specific prior written 40// permission. 41// 42// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 43// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 44// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 45// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 46// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 47// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 48// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 49// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 50// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 51// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 52// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53// 54// 55// TRADEMARK LICENSE 56// 57// KRONA(TM) is a trademark of the Department of Homeland Security, and use 58// of the trademark is subject to the following conditions: 59// 60// * Distribution of the unchanged, official code/software using the 61// KRONA(TM) mark is hereby permitted by the Department of Homeland 62// Security, provided that the software is distributed without charge 63// and modification. 64// 65// * Distribution of altered source code/software using the KRONA(TM) mark 66// is not permitted unless written permission has been granted by the 67// Department of Homeland Security. 68// 69// 70// FOR MORE INFORMATION VISIT 71// 72// http://krona.sourceforge.net 73// 74//----------------------------------------------------------------------------- 75} 76 77var canvas; 78var context; 79var svg; // for snapshot mode 80var collapse = true; 81var collapseCheckBox; 82var collapseLast; 83var compress; 84var compressCheckBox; 85var maxAbsoluteDepthText; 86var maxAbsoluteDepthButtonDecrease; 87var maxAbsoluteDepthButtonIncrease; 88var fontSize = 11; 89var fontSizeText; 90var fontSizeButtonDecrease; 91var fontSizeButtonIncrease; 92var fontSizeLast; 93var shorten; 94var shortenCheckBox; 95var maxAbsoluteDepth; 96var backButton; 97var upButton; 98var forwardButton; 99var snapshotButton; 100var snapshotMode = false; 101var details; 102var detailsName; 103var search; 104var searchResults; 105var nSearchResults; 106var useHueCheckBox; 107var useHueDiv; 108var datasetDropDown; 109var datasetButtonLast; 110var datasetButtonPrev; 111var datasetButtonNext; 112var keyControl; 113var showKeys = true; 114var linkButton; 115var linkText; 116var frame; 117 118// Node references. Note that the meanings of 'selected' and 'focused' are 119// swapped in the docs. 120// 121var head; // the root of the entire tree 122var selectedNode = 0; // the root of the current view 123var focusNode = 0; // a node chosen for more info (single-click) 124var highlightedNode = 0; // mouse hover node 125var highlightingHidden = false; 126var nodes = new Array(); 127var currentNodeID = 0; // to iterate while loading 128 129var nodeHistory = new Array(); 130var nodeHistoryPosition = 0; 131 132var dataEnabled = false; // true when supplemental files are present 133 134// store non-Krona GET variables so they can be passed on to links 135// 136var getVariables = new Array(); 137 138// selectedNodeLast is separate from the history, since we need to check 139// properties of the last node viewed when browsing through the history 140// 141var selectedNodeLast = 0; 142var zoomOut = false; 143 144// temporary zoom-in while holding the mouse button on a wedge 145// 146var quickLook = false; // true when in quick look state 147var mouseDown = false; 148var mouseDownTime; // to detect mouse button hold 149var quickLookHoldLength = 200; 150 151var imageWidth; 152var imageHeight; 153var centerX; 154var centerY; 155var gRadius; 156var updateViewNeeded = false; 157 158// Determines the angle that the pie chart starts at. 90 degrees makes the 159// center label consistent with the children. 160// 161var rotationOffset = Math.PI / 2; 162 163var buffer = 100; 164 165// The maps are the small pie charts showing the current slice being viewed. 166// 167var mapBuffer = 10; 168var mapRadius = 0; 169var maxMapRadius = 25; 170var mapWidth = 150; 171var maxLabelOverhang = Math.PI * 4.18; 172 173// Keys are the labeled boxes for slices in the highest level that are too thin 174// to label. 175// 176var maxKeySizeFactor = 2; // will be multiplied by font size 177var keySize; 178var keys; 179var keyBuffer = 10; 180var currentKey; 181var keyMinTextLeft; 182var keyMinAngle; 183 184var minRingWidthFactor = 5; // will be multiplied by font size 185var maxPossibleDepth; // the theoretical max that can be displayed 186var maxDisplayDepth; // the actual depth that will be displayed 187var headerHeight = 0;//document.getElementById('options').clientHeight; 188var historySpacingFactor = 1.6; // will be multiplied by font size 189var historyAlphaDelta = .25; 190 191// appearance 192// 193var lineOpacity = 0.3; 194var saturation = 0.5; 195var lightnessBase = 0.6; 196var lightnessMax = .8; 197var thinLineWidth = .3; 198var highlightLineWidth = 1.5; 199var labelBoxBuffer = 6; 200var labelBoxRounding = 15; 201var labelWidthFudge = 1.05; // The width of unshortened labels are set slightly 202 // longer than the name width so the animation 203 // finishes faster. 204var fontNormal; 205var fontBold; 206var fontFamily = 'sans-serif'; 207//var fontFaceBold = 'bold Arial'; 208var nodeRadius; 209var angleFactor; 210var tickLength; 211var compressedRadii; 212 213// colors 214// 215var highlightFill = 'rgba(255, 255, 255, .3)'; 216var colorUnclassified = 'rgb(220,220,220)'; 217 218// label staggering 219// 220var labelOffsets; // will store the current offset at each depth 221// 222// This will store pointers to the last node that had a label in each offset (or "track") of a 223// each depth. These will be used to shorten neighboring labels that would overlap. 224// The [nLabelNodes] index will store the last node with a radial label. 225// labelFirstNodes is the same, but to check for going all the way around and 226// overlapping the first labels. 227// 228var labelLastNodes; 229var labelFirstNodes; 230// 231var nLabelOffsets = 3; // the number of offsets to use 232 233var mouseX = -1; 234var mouseY = -1; 235 236// tweening 237// 238var progress = 0; // for tweening; goes from 0 to 1. 239var progressLast = 0; 240var tweenFactor = 0; // progress converted by a curve for a smoother effect. 241var tweenLength = 850; // in ms 242var tweenCurvature = 13; 243// 244// tweenMax is used to scale the sigmoid function so its range is [0,1] for the 245// domain [0,1] 246// 247var tweenMax = 1 / (1 + Math.exp(-tweenCurvature / 2)); 248// 249var tweenStartTime; 250 251// for framerate debug 252// 253var tweenFrames = 0; 254var fpsDisplay = document.getElementById('frameRate'); 255 256// Arrays to translate xml attribute names into displayable attribute names 257// 258var attributes = new Array(); 259// 260var magnitudeIndex; // the index of attribute arrays used for magnitude 261var membersAssignedIndex; 262var membersSummaryIndex; 263 264// For defining gradients 265// 266var hueDisplayName; 267var hueStopPositions; 268var hueStopHues; 269var hueStopText; 270 271// multiple datasets 272// 273var currentDataset = 0; 274var lastDataset = 0; 275var datasets = 1; 276var datasetNames; 277var datasetSelectSize = 30; 278var datasetAlpha = new Tween(0, 0); 279var datasetWidths = new Array(); 280var datasetChanged; 281var datasetSelectWidth = 50; 282 283window.onload = load; 284 285var image; 286var hiddenPattern; 287var loadingImage; 288 289function resize() 290{ 291 imageWidth = window.innerWidth; 292 imageHeight = window.innerHeight; 293 294 if ( ! snapshotMode ) 295 { 296 context.canvas.width = imageWidth; 297 context.canvas.height = imageHeight; 298 } 299 300 if ( datasetDropDown ) 301 { 302 var ratio = 303 (datasetDropDown.offsetTop + datasetDropDown.clientHeight) * 2 / 304 imageHeight; 305 306 if ( ratio > 1 ) 307 { 308 ratio = 1; 309 } 310 311 ratio = Math.sqrt(ratio); 312 313 datasetSelectWidth = 314 (datasetDropDown.offsetLeft + datasetDropDown.clientWidth) * ratio; 315 } 316 var leftMargin = datasets > 1 ? datasetSelectWidth + 30 : 0; 317 var minDimension = imageWidth - mapWidth - leftMargin > imageHeight ? 318 imageHeight : 319 imageWidth - mapWidth - leftMargin; 320 321 maxMapRadius = minDimension * .03; 322 buffer = minDimension * .1; 323 margin = minDimension * .015; 324 centerX = (imageWidth - mapWidth - leftMargin) / 2 + leftMargin; 325 centerY = imageHeight / 2; 326 gRadius = minDimension / 2 - buffer; 327 //context.font = '11px sans-serif'; 328} 329 330function handleResize() 331{ 332 updateViewNeeded = true; 333} 334 335function Attribute() 336{ 337} 338 339function Tween(start, end) 340{ 341 this.start = start; 342 this.end = end; 343 this.current = this.start; 344 345 this.current = function() 346 { 347 if ( progress == 1 || this.start == this.end ) 348 { 349 return this.end; 350 } 351 else 352 { 353 return this.start + tweenFactor * (this.end - this.start); 354 } 355 }; 356 357 this.setTarget = function(target) 358 { 359 this.start = this.current(); 360 this.end = target; 361 } 362} 363 364function Node() 365{ 366 this.id = currentNodeID; 367 currentNodeID++; 368 nodes[this.id] = this; 369 370 this.angleStart = new Tween(Math.PI, 0); 371 this.angleEnd = new Tween(Math.PI, 0); 372 this.radiusInner = new Tween(1, 1); 373 this.labelRadius = new Tween(1, 1); 374 this.labelWidth = new Tween(0, 0); 375 this.scale = new Tween(1, 1); // TEMP 376 this.radiusOuter = new Tween(1, 1); 377 378 this.r = new Tween(255, 255); 379 this.g = new Tween(255, 255); 380 this.b = new Tween(255, 255); 381 382 this.alphaLabel = new Tween(0, 1); 383 this.alphaLine = new Tween(0, 1); 384 this.alphaArc = new Tween(0, 0); 385 this.alphaWedge = new Tween(0, 1); 386 this.alphaOther = new Tween(0, 1); 387 this.alphaPattern = new Tween(0, 0); 388 this.children = Array(); 389 this.parent = 0; 390 391 this.attributes = new Array(attributes.length); 392 393 this.addChild = function(child) 394 { 395 this.children.push(child); 396 }; 397 398 this.addLabelNode = function(depth, labelOffset) 399 { 400 if ( labelHeadNodes[depth][labelOffset] == 0 ) 401 { 402 // this will become the head node for this list 403 404 labelHeadNodes[depth][labelOffset] = this; 405 this.labelPrev = this; 406 } 407 408 var head = labelHeadNodes[depth][labelOffset]; 409 410 this.labelNext = head; 411 this.labelPrev = head.labelPrev; 412 head.labelPrev.labelNext = this; 413 head.labelPrev = this; 414 } 415 416 this.canDisplayDepth = function() 417 { 418 // whether this node is at a depth that can be displayed, according 419 // to the max absolute depth 420 421 return this.depth <= maxAbsoluteDepth; 422 } 423 424 this.canDisplayHistory = function() 425 { 426 var radiusInner; 427 428 if ( compress ) 429 { 430 radiusInner = compressedRadii[0]; 431 } 432 else 433 { 434 radiusInner = nodeRadius; 435 } 436 437 return ( 438 -this.labelRadius.end * gRadius + 439 historySpacingFactor * fontSize / 2 < 440 radiusInner * gRadius 441 ); 442 } 443 444 this.canDisplayLabelCurrent = function() 445 { 446 return ( 447 (this.angleEnd.current() - this.angleStart.current()) * 448 (this.radiusInner.current() * gRadius + gRadius) >= 449 minWidth()); 450 } 451 452 this.checkHighlight = function() 453 { 454 if ( this.children.length == 0 && this == focusNode ) 455 { 456 //return false; 457 } 458 459 if ( this.hide ) 460 { 461 return false; 462 } 463 464 if ( this.radiusInner.end == 1 ) 465 { 466 // compressed to the outside; don't check 467 468 return false; 469 } 470 471 var highlighted = false; 472 473 var angleStartCurrent = this.angleStart.current() + rotationOffset; 474 var angleEndCurrent = this.angleEnd.current() + rotationOffset; 475 var radiusInner = this.radiusInner.current() * gRadius; 476 477 for ( var i = 0; i < this.children.length; i++ ) 478 { 479 highlighted = this.children[i].checkHighlight(); 480 481 if ( highlighted ) 482 { 483 return true; 484 } 485 } 486 487 if ( this != selectedNode && ! this.getCollapse() ) 488 { 489 context.beginPath(); 490 context.arc(0, 0, radiusInner, angleStartCurrent, angleEndCurrent, false); 491 context.arc(0, 0, gRadius, angleEndCurrent, angleStartCurrent, true); 492 context.closePath(); 493 494 if ( context.isPointInPath(mouseX - centerX, mouseY - centerY) ) 495 { 496 highlighted = true; 497 } 498 499 if 500 ( 501 ! highlighted && 502 (angleEndCurrent - angleStartCurrent) * 503 (radiusInner + gRadius) < 504 minWidth() && 505 this.getDepth() == selectedNode.getDepth() + 1 506 ) 507 { 508 if ( showKeys && this.checkHighlightKey() ) 509 { 510 highlighted = true; 511 } 512 } 513 } 514 515 if ( highlighted ) 516 { 517 if ( this != highlightedNode ) 518 { 519 // document.body.style.cursor='pointer'; 520 } 521 522 highlightedNode = this; 523 } 524 525 return highlighted; 526 } 527 528 this.checkHighlightCenter = function() 529 { 530 if ( ! this.canDisplayHistory() ) 531 { 532 return; 533 } 534 535 var cx = centerX; 536 var cy = centerY - this.labelRadius.end * gRadius; 537 //var dim = context.measureText(this.name); 538 539 var width = this.nameWidth; 540 541 if ( this.searchResultChildren() ) 542 { 543 var results = searchResultString(this.searchResultChildren()); 544 var dim = context.measureText(results); 545 width += dim.width; 546 } 547 548 if 549 ( 550 mouseX > cx - width / 2 && 551 mouseX < cx + width / 2 && 552 mouseY > cy - historySpacingFactor * fontSize / 2 && 553 mouseY < cy + historySpacingFactor * fontSize / 2 554 ) 555 { 556 highlightedNode = this; 557 return; 558 } 559 560 if ( this.getParent() ) 561 { 562 this.getParent().checkHighlightCenter(); 563 } 564 } 565 566 this.checkHighlightKey = function() 567 { 568 var offset = keyOffset(); 569 570 var xMin = imageWidth - keySize - margin - this.keyNameWidth - keyBuffer; 571 var xMax = imageWidth - margin; 572 var yMin = offset; 573 var yMax = offset + keySize; 574 575 currentKey++; 576 577 return ( 578 mouseX > xMin && 579 mouseX < xMax && 580 mouseY > yMin && 581 mouseY < yMax); 582 } 583 584 this.checkHighlightMap = function() 585 { 586 if ( this.parent ) 587 { 588 this.parent.checkHighlightMap(); 589 } 590 591 if ( this.getCollapse() || this == focusNode ) 592 { 593 return; 594 } 595 596 var box = this.getMapPosition(); 597 598 if 599 ( 600 mouseX > box.x - mapRadius && 601 mouseX < box.x + mapRadius && 602 mouseY > box.y - mapRadius && 603 mouseY < box.y + mapRadius 604 ) 605 { 606 highlightedNode = this; 607 } 608 } 609 610/* this.collapse = function() 611 { 612 for (var i = 0; i < this.children.length; i++ ) 613 { 614 this.children[i] = this.children[i].collapse(); 615 } 616 617 if 618 ( 619 this.children.length == 1 && 620 this.children[0].magnitude == this.magnitude 621 ) 622 { 623 this.children[0].parent = this.parent; 624 this.children[0].getDepth() = this.parent.getDepth() + 1; 625 return this.children[0]; 626 } 627 else 628 { 629 return this; 630 } 631 } 632*/ 633 this.draw = function(labelMode, selected, searchHighlighted) 634 { 635 var depth = this.getDepth() - selectedNode.getDepth() + 1; 636// var hidden = false; 637 638 if ( selectedNode == this ) 639 { 640 selected = true; 641 } 642 643 var angleStartCurrent = this.angleStart.current() + rotationOffset; 644 var angleEndCurrent = this.angleEnd.current() + rotationOffset; 645 var radiusInner = this.radiusInner.current() * gRadius; 646 var canDisplayLabelCurrent = this.canDisplayLabelCurrent(); 647 var hiddenSearchResults = false; 648 649/* if ( ! this.hide ) 650 { 651 for ( var i = 0; i < this.children.length; i++ ) 652 { 653 if ( this.children[i].hide && this.children[i].searchResults ) 654 { 655 hiddenSearchResults = true; 656 } 657 } 658 } 659*/ 660 var drawChildren = 661 ( ! this.hide || ! this.hidePrev && progress < 1 ) && 662 ( ! this.hideAlone || ! this.hideAlonePrev && progress < 1 ); 663 664// if ( this.alphaWedge.current() > 0 || this.alphaLabel.current() > 0 ) 665 { 666 var lastChildAngleEnd; 667 668 if ( this.hasChildren() )//canDisplayChildren ) 669 { 670 lastChildAngleEnd = 671 this.children[this.children.length - 1].angleEnd.current() 672 + rotationOffset; 673 } 674 675 if ( labelMode ) 676 { 677 var drawRadial = 678 !( 679 this.parent && 680 this.parent != selectedNode && 681 angleEndCurrent == this.parent.angleEnd.current() + rotationOffset 682 ); 683 684 if ( angleStartCurrent != angleEndCurrent ) 685 { 686 this.drawLines(angleStartCurrent, angleEndCurrent, radiusInner, drawRadial, selected); 687 } 688 689 var alphaOtherCurrent = this.alphaOther.current(); 690 var childRadiusInner; 691 692 if ( this == selectedNode || alphaOtherCurrent ) 693 { 694 childRadiusInner = 695 this.children[this.children.length - 1].radiusInner.current() * gRadius; 696 } 697 698 if ( this == selectedNode ) 699 { 700 this.drawReferenceRings(childRadiusInner); 701 } 702 703 if 704 ( 705 selected && 706 ! searchHighlighted && 707 this != selectedNode && 708 ( 709 this.isSearchResult || 710 this.hideAlone && this.searchResultChildren() || 711 false 712// this.hide && 713// this.containsSearchResult 714 ) 715 ) 716 { 717 context.globalAlpha = this.alphaWedge.current(); 718 719 drawWedge 720 ( 721 angleStartCurrent, 722 angleEndCurrent, 723 radiusInner, 724 gRadius, 725 highlightFill, 726 0, 727 true 728 ); 729 730 if 731 ( 732 this.keyed && 733 ! showKeys && 734 this.searchResults && 735 ! searchHighlighted && 736 this != highlightedNode && 737 this != focusNode 738 ) 739 { 740 var angle = (angleEndCurrent + angleStartCurrent) / 2; 741 this.drawLabel(angle, true, false, true, true); 742 } 743 744 //this.drawHighlight(false); 745 searchHighlighted = true; 746 } 747 748 if 749 ( 750 this == selectedNode || 751// true 752 //(canDisplayLabelCurrent) && 753 this != highlightedNode && 754 this != focusNode 755 ) 756 { 757 if ( this.radial != this.radialPrev && this.alphaLabel.end == 1 ) 758 { 759 context.globalAlpha = tweenFactor; 760 } 761 else 762 { 763 context.globalAlpha = this.alphaLabel.current(); 764 } 765 766 this.drawLabel 767 ( 768 (angleStartCurrent + angleEndCurrent) / 2, 769 this.hideAlone && this.searchResultChildren() || 770 (this.isSearchResult || hiddenSearchResults) && selected, 771 this == selectedNode && ! this.radial, 772 selected, 773 this.radial 774 ); 775 776 if ( this.radial != this.radialPrev && this.alphaLabel.start == 1 && progress < 1 ) 777 { 778 context.globalAlpha = 1 - tweenFactor; 779 780 this.drawLabel 781 ( 782 (angleStartCurrent + angleEndCurrent) / 2, 783 (this.isSearchResult || hiddenSearchResults) && selected, 784 this == selectedNodeLast && ! this.radialPrev, 785 selected, 786 this.radialPrev 787 ); 788 } 789 } 790 791 if 792 ( 793 alphaOtherCurrent && 794 lastChildAngleEnd != null 795 ) 796 { 797 if 798 ( 799 (angleEndCurrent - lastChildAngleEnd) * 800 (childRadiusInner + gRadius) >= 801 minWidth() 802 ) 803 { 804 //context.font = fontNormal; 805 context.globalAlpha = this.alphaOther.current(); 806 807 drawTextPolar 808 ( 809 this.getUnclassifiedText(), 810 this.getUnclassifiedPercentage(), 811 (lastChildAngleEnd + angleEndCurrent) / 2, 812 (childRadiusInner + gRadius) / 2, 813 true, 814 false, 815 false, 816 0, 817 0 818 ); 819 } 820 } 821 822 if ( this == selectedNode && this.keyUnclassified && showKeys ) 823 { 824 this.drawKey 825 ( 826 (lastChildAngleEnd + angleEndCurrent) / 2, 827 false, 828 false 829 ); 830 } 831 } 832 else 833 { 834 var alphaWedgeCurrent = this.alphaWedge.current(); 835 836 if ( alphaWedgeCurrent || this.alphaOther.current() ) 837 { 838 var currentR = this.r.current(); 839 var currentG = this.g.current(); 840 var currentB = this.b.current(); 841 842 var fill = rgbText(currentR, currentG, currentB); 843 844 var radiusOuter; 845 var lastChildAngle; 846 var truncateWedge = 847 ( 848 this.hasChildren() && 849 ! this.keyed && 850 (compress || depth < maxDisplayDepth) && 851 drawChildren 852 ); 853 854 if ( truncateWedge ) 855 { 856 radiusOuter = this.children[0].radiusInner.current() * gRadius; 857 } 858 else 859 { 860 radiusOuter = gRadius; 861 } 862 /* 863 if ( this.hasChildren() ) 864 { 865 radiusOuter = this.children[0].getUncollapsed().radiusInner.current() * gRadius + 1; 866 } 867 else 868 { // TEMP 869 radiusOuter = radiusInner + nodeRadius * gRadius; 870 871 if ( radiusOuter > gRadius ) 872 { 873 radiusOuter = gRadius; 874 } 875 } 876 */ 877 context.globalAlpha = alphaWedgeCurrent; 878 879 if ( radiusInner != radiusOuter ) 880 { 881 drawWedge 882 ( 883 angleStartCurrent, 884 angleEndCurrent, 885 radiusInner, 886 radiusOuter,//this.radiusOuter.current() * gRadius, 887 //'rgba(0, 200, 0, .1)', 888 fill, 889 this.alphaPattern.current() 890 ); 891 892 if ( truncateWedge ) 893 { 894 // fill in the extra space if the sum of our childrens' 895 // magnitudes is less than ours 896 897 if ( lastChildAngleEnd < angleEndCurrent )//&& false) // TEMP 898 { 899 if ( radiusOuter > 1 ) 900 { 901 // overlap slightly to hide the seam 902 903 // radiusOuter -= 1; 904 } 905 906 if ( alphaWedgeCurrent < 1 ) 907 { 908 context.globalAlpha = this.alphaOther.current(); 909 drawWedge 910 ( 911 lastChildAngleEnd, 912 angleEndCurrent, 913 radiusOuter, 914 gRadius, 915 colorUnclassified, 916 0 917 ); 918 context.globalAlpha = alphaWedgeCurrent; 919 } 920 921 drawWedge 922 ( 923 lastChildAngleEnd, 924 angleEndCurrent, 925 radiusOuter, 926 gRadius,//this.radiusOuter.current() * gRadius, 927 //'rgba(200, 0, 0, .1)', 928 fill, 929 this.alphaPattern.current() 930 ); 931 } 932 } 933 934 if ( radiusOuter < gRadius ) 935 { 936 // patch up the seam 937 // 938 context.beginPath(); 939 context.arc(0, 0, radiusOuter, angleStartCurrent/*lastChildAngleEnd*/, angleEndCurrent, false); 940 context.strokeStyle = fill; 941 context.lineWidth = 1; 942 context.stroke(); 943 } 944 } 945 946 if ( this.keyed && selected && showKeys )//&& progress == 1 ) 947 { 948 this.drawKey 949 ( 950 (angleStartCurrent + angleEndCurrent) / 2, 951 ( 952 this == highlightedNode || 953 this == focusNode || 954 this.searchResults 955 ), 956 this == highlightedNode || this == focusNode 957 ); 958 } 959 } 960 } 961 } 962 963 if ( drawChildren ) 964 { 965 // draw children 966 // 967 for ( var i = 0; i < this.children.length; i++ ) 968 { 969 if ( this.drawHiddenChildren(i, selected, labelMode, searchHighlighted) ) 970 { 971 i = this.children[i].hiddenEnd; 972 } 973 else 974 { 975 this.children[i].draw(labelMode, selected, searchHighlighted); 976 } 977 } 978 } 979 }; 980 981 this.drawHiddenChildren = function 982 ( 983 firstHiddenChild, 984 selected, 985 labelMode, 986 searchHighlighted 987 ) 988 { 989 var firstChild = this.children[firstHiddenChild]; 990 991 if ( firstChild.hiddenEnd == null || firstChild.radiusInner.current() == 1 ) 992 { 993 return false; 994 } 995 996 for ( var i = firstHiddenChild; i < firstChild.hiddenEnd; i++ ) 997 { 998 if ( ! this.children[i].hide || ! this.children[i].hidePrev && progress < 1 ) 999 { 1000 return false; 1001 } 1002 } 1003 1004 var angleStart = firstChild.angleStart.current() + rotationOffset; 1005 var lastChild = this.children[firstChild.hiddenEnd]; 1006 var angleEnd = lastChild.angleEnd.current() + rotationOffset; 1007 var radiusInner = gRadius * firstChild.radiusInner.current(); 1008 var hiddenChildren = firstChild.hiddenEnd - firstHiddenChild + 1; 1009 1010 if ( labelMode ) 1011 { 1012 var hiddenSearchResults = 0; 1013 1014 for ( var i = firstHiddenChild; i <= firstChild.hiddenEnd; i++ ) 1015 { 1016 hiddenSearchResults += this.children[i].searchResults; 1017 } 1018 1019 if 1020 ( 1021 selected && 1022 (angleEnd - angleStart) * 1023 (gRadius + gRadius) >= 1024 minWidth() || 1025 hiddenSearchResults 1026 ) 1027 { 1028 context.globalAlpha = this.alphaWedge.current(); 1029 1030 this.drawHiddenLabel 1031 ( 1032 angleStart, 1033 angleEnd, 1034 hiddenChildren, 1035 hiddenSearchResults 1036 ); 1037 } 1038 } 1039 1040 var drawWedges = true; 1041 1042 for ( var i = firstHiddenChild; i <= firstChild.hiddenEnd; i++ ) 1043 { 1044 // all hidden children must be completely hidden to draw together 1045 1046 if ( this.children[i].alphaPattern.current() != this.children[i].alphaWedge.current() ) 1047 { 1048 drawWedges = false; 1049 break; 1050 } 1051 } 1052 1053 if ( labelMode ) 1054 { 1055 if ( drawWedges ) 1056 { 1057 var drawRadial = (angleEnd < this.angleEnd.current() + rotationOffset); 1058 this.drawLines(angleStart, angleEnd, radiusInner, drawRadial); 1059 } 1060 1061 if ( hiddenSearchResults && ! searchHighlighted ) 1062 { 1063 drawWedge 1064 ( 1065 angleStart, 1066 angleEnd, 1067 radiusInner, 1068 gRadius,//this.radiusOuter.current() * gRadius, 1069 highlightFill, 1070 0, 1071 true 1072 ); 1073 } 1074 } 1075 else if ( drawWedges ) 1076 { 1077 context.globalAlpha = this.alphaWedge.current(); 1078 1079 var fill = rgbText 1080 ( 1081 firstChild.r.current(), 1082 firstChild.g.current(), 1083 firstChild.b.current() 1084 ); 1085 1086 drawWedge 1087 ( 1088 angleStart, 1089 angleEnd, 1090 radiusInner, 1091 gRadius,//this.radiusOuter.current() * gRadius, 1092 fill, 1093 context.globalAlpha, 1094 false 1095 ); 1096 } 1097 1098 return drawWedges; 1099 } 1100 1101 this.drawHiddenLabel = function(angleStart, angleEnd, value, hiddenSearchResults) 1102 { 1103 var textAngle = (angleStart + angleEnd) / 2; 1104 var labelRadius = gRadius + fontSize;//(radiusInner + radius) / 2; 1105 1106 drawTick(gRadius - fontSize * .75, fontSize * 1.5, textAngle); 1107 drawTextPolar 1108 ( 1109 value.toString() + ' more', 1110 0, // inner text 1111 textAngle, 1112 labelRadius, 1113 true, // radial 1114 hiddenSearchResults, // bubble 1115 this == highlightedNode || this == focusNode, // bold 1116 false, 1117 hiddenSearchResults 1118 ); 1119 } 1120 1121 this.drawHighlight = function(bold) 1122 { 1123 var angleStartCurrent = this.angleStart.current() + rotationOffset; 1124 var angleEndCurrent = this.angleEnd.current() + rotationOffset; 1125 var radiusInner = this.radiusInner.current() * gRadius; 1126 1127 //this.setHighlightStyle(); 1128 1129 if ( this == focusNode && this == highlightedNode && this.hasChildren() ) 1130 { 1131// context.fillStyle = "rgba(255, 255, 255, .3)"; 1132 arrow 1133 ( 1134 angleStartCurrent, 1135 angleEndCurrent, 1136 radiusInner 1137 ); 1138 } 1139 else 1140 { 1141 drawWedge 1142 ( 1143 angleStartCurrent, 1144 angleEndCurrent, 1145 radiusInner, 1146 gRadius, 1147 highlightFill, 1148 0, 1149 true 1150 ); 1151 } 1152 1153 // check if hidden children should be highlighted 1154 // 1155 for ( var i = 0; i < this.children.length; i++ ) 1156 { 1157 if 1158 ( 1159 this.children[i].getDepth() - selectedNode.getDepth() + 1 <= 1160 maxDisplayDepth && 1161 this.children[i].hiddenEnd != null 1162 ) 1163 { 1164 var firstChild = this.children[i]; 1165 var lastChild = this.children[firstChild.hiddenEnd]; 1166 var hiddenAngleStart = firstChild.angleStart.current() + rotationOffset; 1167 var hiddenAngleEnd = lastChild.angleEnd.current() + rotationOffset; 1168 var hiddenRadiusInner = gRadius * firstChild.radiusInner.current(); 1169 1170 drawWedge 1171 ( 1172 hiddenAngleStart, 1173 hiddenAngleEnd, 1174 hiddenRadiusInner, 1175 gRadius, 1176 'rgba(255, 255, 255, .3)', 1177 0, 1178 true 1179 ); 1180 1181 if ( ! this.searchResults ) 1182 { 1183 this.drawHiddenLabel 1184 ( 1185 hiddenAngleStart, 1186 hiddenAngleEnd, 1187 firstChild.hiddenEnd - i + 1 1188 ); 1189 } 1190 1191 i = firstChild.hiddenEnd; 1192 } 1193 } 1194 1195// context.strokeStyle = 'black'; 1196 context.fillStyle = 'black'; 1197 1198 var highlight = ! ( progress < 1 && zoomOut && this == selectedNodeLast ); 1199 1200 var angle = (angleEndCurrent + angleStartCurrent) / 2; 1201 1202 if ( ! (this.keyed && showKeys) ) 1203 { 1204 this.drawLabel(angle, true, bold, true, this.radial); 1205 } 1206 } 1207 1208 this.drawHighlightCenter = function() 1209 { 1210 if ( ! this.canDisplayHistory() ) 1211 { 1212 return; 1213 } 1214 1215 context.lineWidth = highlightLineWidth; 1216 context.strokeStyle = 'black'; 1217 context.fillStyle = "rgba(255, 255, 255, .6)"; 1218 1219 context.fillStyle = 'black'; 1220 this.drawLabel(3 * Math.PI / 2, true, true, false); 1221 context.font = fontNormal; 1222 } 1223 1224 this.drawKey = function(angle, highlight, bold) 1225 { 1226 var offset = keyOffset(); 1227 var color; 1228 var patternAlpha = this.alphaPattern.end; 1229 var boxLeft = imageWidth - keySize - margin; 1230 var textY = offset + keySize / 2; 1231 1232 var label; 1233 var keyNameWidth; 1234 1235 if ( this == selectedNode ) 1236 { 1237 color = colorUnclassified; 1238 label = 1239 this.getUnclassifiedText() + 1240 ' ' + 1241 this.getUnclassifiedPercentage(); 1242 keyNameWidth = measureText(label, false); 1243 } 1244 else 1245 { 1246 label = this.keyLabel; 1247 color = rgbText(this.r.end, this.g.end, this.b.end); 1248 1249 if ( highlight ) 1250 { 1251 if ( this.searchResultChildren() ) 1252 { 1253 label = label + searchResultString(this.searchResultChildren()); 1254 } 1255 1256 keyNameWidth = measureText(label, bold); 1257 } 1258 else 1259 { 1260 keyNameWidth = this.keyNameWidth; 1261 } 1262 } 1263 1264 var textLeft = boxLeft - keyBuffer - keyNameWidth - fontSize / 2; 1265 var labelLeft = textLeft; 1266 1267 if ( labelLeft > keyMinTextLeft - fontSize / 2 ) 1268 { 1269 keyMinTextLeft -= fontSize / 2; 1270 1271 if ( keyMinTextLeft < centerX - gRadius + fontSize / 2 ) 1272 { 1273 keyMinTextLeft = centerX - gRadius + fontSize / 2; 1274 } 1275 1276 labelLeft = keyMinTextLeft; 1277 } 1278 1279 var lineX = new Array(); 1280 var lineY = new Array(); 1281 1282 var bendRadius; 1283 var keyAngle = Math.atan((textY - centerY) / (labelLeft - centerX)); 1284 var arcAngle; 1285 1286 if ( keyAngle < 0 ) 1287 { 1288 keyAngle += Math.PI; 1289 } 1290 1291 if ( keyMinAngle == 0 || angle < keyMinAngle ) 1292 { 1293 keyMinAngle = angle; 1294 } 1295 1296 if ( angle > Math.PI && keyMinAngle > Math.PI ) 1297 { 1298 // allow lines to come underneath the chart 1299 1300 angle -= Math.PI * 2; 1301 } 1302 1303 lineX.push(Math.cos(angle) * gRadius); 1304 lineY.push(Math.sin(angle) * gRadius); 1305 1306 if ( angle < keyAngle && textY > centerY + Math.sin(angle) * (gRadius + buffer * (currentKey - 1) / (keys + 1) / 2 + buffer / 2) ) 1307 { 1308 bendRadius = gRadius + buffer - buffer * currentKey / (keys + 1) / 2; 1309 } 1310 else 1311 { 1312 bendRadius = gRadius + buffer * currentKey / (keys + 1) / 2 + buffer / 2; 1313 } 1314 1315 var outside = 1316 Math.sqrt 1317 ( 1318 Math.pow(labelLeft - centerX, 2) + 1319 Math.pow(textY - centerY, 2) 1320 ) > bendRadius; 1321 1322 if ( ! outside ) 1323 { 1324 arcAngle = Math.asin((textY - centerY) / bendRadius); 1325 1326 keyMinTextLeft = min(keyMinTextLeft, centerX + bendRadius * Math.cos(arcAngle) - fontSize / 2); 1327 1328 if ( labelLeft < textLeft && textLeft > centerX + bendRadius * Math.cos(arcAngle) ) 1329 { 1330 lineX.push(textLeft - centerX); 1331 lineY.push(textY - centerY); 1332 } 1333 } 1334 else 1335 { 1336 keyMinTextLeft = min(keyMinTextLeft, labelLeft - fontSize / 2); 1337 1338 if ( angle < keyAngle ) 1339 { 1340 // flip everything over y = x 1341 // 1342 arcAngle = Math.PI / 2 - keyLineAngle 1343 ( 1344 Math.PI / 2 - angle, 1345 Math.PI / 2 - keyAngle, 1346 bendRadius, 1347 textY - centerY, 1348 labelLeft - centerX, 1349 lineY, 1350 lineX 1351 ); 1352 1353 } 1354 else 1355 { 1356 arcAngle = keyLineAngle 1357 ( 1358 angle, 1359 keyAngle, 1360 bendRadius, 1361 labelLeft - centerX, 1362 textY - centerY, 1363 lineX, 1364 lineY 1365 ); 1366 } 1367 } 1368 1369 if ( labelLeft > centerX + bendRadius * Math.cos(arcAngle) || 1370 textY > centerY + bendRadius * Math.sin(arcAngle) + .01) 1371// if ( outside || ) 1372 { 1373 lineX.push(labelLeft - centerX); 1374 lineY.push(textY - centerY); 1375 1376 if ( textLeft != labelLeft ) 1377 { 1378 lineX.push(textLeft - centerX); 1379 lineY.push(textY - centerY); 1380 } 1381 } 1382 1383 context.globalAlpha = this.alphaWedge.current(); 1384 1385 if ( snapshotMode ) 1386 { 1387 var labelSVG; 1388 1389 if ( this == selectedNode ) 1390 { 1391 labelSVG = 1392 this.getUnclassifiedText() + 1393 spacer() + 1394 this.getUnclassifiedPercentage(); 1395 } 1396 else 1397 { 1398 labelSVG = this.name + spacer() + this.getPercentage() + '%'; 1399 } 1400 1401 svg += 1402 '<rect fill="' + color + '" ' + 1403 'x="' + boxLeft + '" y="' + offset + 1404 '" width="' + keySize + '" height="' + keySize + '"/>'; 1405 1406 if ( patternAlpha ) 1407 { 1408 svg += 1409 '<rect fill="url(#hiddenPattern)" style="stroke:none" ' + 1410 'x="' + boxLeft + '" y="' + offset + 1411 '" width="' + keySize + '" height="' + keySize + '"/>'; 1412 } 1413 1414 svg += 1415 '<path class="line' + 1416 (highlight ? ' highlight' : '') + 1417 '" d="M ' + (lineX[0] + centerX) + ',' + 1418 (lineY[0] + centerY); 1419 1420 if ( angle != arcAngle ) 1421 { 1422 svg += 1423 ' L ' + (centerX + bendRadius * Math.cos(angle)) + ',' + 1424 (centerY + bendRadius * Math.sin(angle)) + 1425 ' A ' + bendRadius + ',' + bendRadius + ' 0 ' + 1426 '0,' + (angle > arcAngle ? '0' : '1') + ' ' + 1427 (centerX + bendRadius * Math.cos(arcAngle)) + ',' + 1428 (centerY + bendRadius * Math.sin(arcAngle)); 1429 } 1430 1431 for ( var i = 1; i < lineX.length; i++ ) 1432 { 1433 svg += 1434 ' L ' + (centerX + lineX[i]) + ',' + 1435 (centerY + lineY[i]); 1436 } 1437 1438 svg += '"/>'; 1439 1440 if ( highlight ) 1441 { 1442 if ( this.searchResultChildren() ) 1443 { 1444 labelSVG = labelSVG + searchResultString(this.searchResultChildren()); 1445 } 1446 1447 drawBubbleSVG 1448 ( 1449 boxLeft - keyBuffer - keyNameWidth - fontSize / 2, 1450 textY - fontSize, 1451 keyNameWidth + fontSize, 1452 fontSize * 2, 1453 fontSize, 1454 0 1455 ); 1456 1457 if ( this.isSearchResult ) 1458 { 1459 drawSearchHighlights 1460 ( 1461 label, 1462 boxLeft - keyBuffer - keyNameWidth, 1463 textY, 1464 0 1465 ) 1466 } 1467 } 1468 1469 svg += svgText(labelSVG, boxLeft - keyBuffer, textY, 'end', bold); 1470 } 1471 else 1472 { 1473 context.fillStyle = color; 1474 context.translate(-centerX, -centerY); 1475 context.strokeStyle = 'black'; 1476 context.globalAlpha = 1;//this.alphaWedge.current(); 1477 1478 context.fillRect(boxLeft, offset, keySize, keySize); 1479 1480 if ( patternAlpha ) 1481 { 1482 context.globalAlpha = patternAlpha; 1483 context.fillStyle = hiddenPattern; 1484 1485 // make clipping box for Firefox performance 1486 context.beginPath(); 1487 context.moveTo(boxLeft, offset); 1488 context.lineTo(boxLeft + keySize, offset); 1489 context.lineTo(boxLeft + keySize, offset + keySize); 1490 context.lineTo(boxLeft, offset + keySize); 1491 context.closePath(); 1492 context.save(); 1493 context.clip(); 1494 1495 context.fillRect(boxLeft, offset, keySize, keySize); 1496 context.fillRect(boxLeft, offset, keySize, keySize); 1497 1498 context.restore(); // remove clipping region 1499 } 1500 1501 if ( highlight ) 1502 { 1503 this.setHighlightStyle(); 1504 context.fillRect(boxLeft, offset, keySize, keySize); 1505 } 1506 else 1507 { 1508 context.lineWidth = thinLineWidth; 1509 } 1510 1511 context.strokeRect(boxLeft, offset, keySize, keySize); 1512 1513 if ( lineX.length ) 1514 { 1515 context.beginPath(); 1516 context.moveTo(lineX[0] + centerX, lineY[0] + centerY); 1517 1518 context.arc(centerX, centerY, bendRadius, angle, arcAngle, angle > arcAngle); 1519 1520 for ( var i = 1; i < lineX.length; i++ ) 1521 { 1522 context.lineTo(lineX[i] + centerX, lineY[i] + centerY); 1523 } 1524 1525 context.globalAlpha = this == selectedNode ? 1526 this.children[0].alphaWedge.current() : 1527 this.alphaWedge.current(); 1528 context.lineWidth = highlight ? highlightLineWidth : thinLineWidth; 1529 context.stroke(); 1530 context.globalAlpha = 1; 1531 } 1532 1533 if ( highlight ) 1534 { 1535 drawBubbleCanvas 1536 ( 1537 boxLeft - keyBuffer - keyNameWidth - fontSize / 2, 1538 textY - fontSize, 1539 keyNameWidth + fontSize, 1540 fontSize * 2, 1541 fontSize, 1542 0 1543 ); 1544 1545 if ( this.isSearchResult ) 1546 { 1547 drawSearchHighlights 1548 ( 1549 label, 1550 boxLeft - keyBuffer - keyNameWidth, 1551 textY, 1552 0 1553 ) 1554 } 1555 } 1556 1557 drawText(label, boxLeft - keyBuffer, offset + keySize / 2, 0, 'end', bold); 1558 1559 context.translate(centerX, centerY); 1560 } 1561 1562 currentKey++; 1563 } 1564 1565 this.drawLabel = function(angle, bubble, bold, selected, radial) 1566 { 1567 if ( context.globalAlpha == 0 ) 1568 { 1569 return; 1570 } 1571 1572 var innerText; 1573 var label; 1574 var radius; 1575 1576 if ( radial ) 1577 { 1578 radius = (this.radiusInner.current() + 1) * gRadius / 2; 1579 } 1580 else 1581 { 1582 radius = this.labelRadius.current() * gRadius; 1583 } 1584 1585 if ( radial && (selected || bubble ) ) 1586 { 1587 var percentage = this.getPercentage(); 1588 innerText = percentage + '%'; 1589 } 1590 1591 if 1592 ( 1593 ! radial && 1594 this != selectedNode && 1595 ! bubble && 1596 ( !zoomOut || this != selectedNodeLast) 1597 ) 1598 { 1599 label = this.shortenLabel(); 1600 } 1601 else 1602 { 1603 label = this.name; 1604 } 1605 1606 var flipped = drawTextPolar 1607 ( 1608 label, 1609 innerText, 1610 angle, 1611 radius, 1612 radial, 1613 bubble, 1614 bold, 1615// this.isSearchResult && this.shouldAddSearchResultsString() && (!selected || this == selectedNode || highlight), 1616 this.isSearchResult && (!selected || this == selectedNode || bubble), 1617 (this.hideAlone || !selected || this == selectedNode ) ? this.searchResultChildren() : 0 1618 ); 1619 1620 var depth = this.getDepth() - selectedNode.getDepth() + 1; 1621 1622 if 1623 ( 1624 ! radial && 1625 ! bubble && 1626 this != selectedNode && 1627 this.angleEnd.end != this.angleStart.end && 1628 nLabelOffsets[depth - 2] > 2 && 1629 this.labelWidth.current() > (this.angleEnd.end - this.angleStart.end) * Math.abs(radius) && 1630 ! ( zoomOut && this == selectedNodeLast ) && 1631 this.labelRadius.end > 0 1632 ) 1633 { 1634 // name extends beyond wedge; draw tick mark towards the central 1635 // radius for easier identification 1636 1637 var radiusCenter = compress ? 1638 (compressedRadii[depth - 1] + compressedRadii[depth - 2]) / 2 : 1639 (depth - .5) * nodeRadius; 1640 1641 if ( this.labelRadius.end > radiusCenter ) 1642 { 1643 if ( flipped ) 1644 { 1645 drawTick(radius - tickLength * 1.4 , tickLength, angle); 1646 } 1647 else 1648 { 1649 drawTick(radius - tickLength * 1.7, tickLength, angle); 1650 } 1651 } 1652 else 1653 { 1654 if ( flipped ) 1655 { 1656 drawTick(radius + tickLength * .7, tickLength, angle); 1657 } 1658 else 1659 { 1660 drawTick(radius + tickLength * .4, tickLength, angle); 1661 } 1662 } 1663 } 1664 } 1665 1666 this.drawLines = function(angleStart, angleEnd, radiusInner, drawRadial, selected) 1667 { 1668 if ( snapshotMode ) 1669 { 1670 if ( this != selectedNode) 1671 { 1672 if ( angleEnd == angleStart + Math.PI * 2 ) 1673 { 1674 // fudge to prevent overlap, which causes arc ambiguity 1675 // 1676 angleEnd -= .1 / gRadius; 1677 } 1678 1679 var longArc = angleEnd - angleStart > Math.PI ? 1 : 0; 1680 1681 var x1 = centerX + radiusInner * Math.cos(angleStart); 1682 var y1 = centerY + radiusInner * Math.sin(angleStart); 1683 1684 var x2 = centerX + gRadius * Math.cos(angleStart); 1685 var y2 = centerY + gRadius * Math.sin(angleStart); 1686 1687 var x3 = centerX + gRadius * Math.cos(angleEnd); 1688 var y3 = centerY + gRadius * Math.sin(angleEnd); 1689 1690 var x4 = centerX + radiusInner * Math.cos(angleEnd); 1691 var y4 = centerY + radiusInner * Math.sin(angleEnd); 1692 1693 if ( this.alphaArc.end ) 1694 { 1695 var dArray = 1696 [ 1697 " M ", x4, ",", y4, 1698 " A ", radiusInner, ",", radiusInner, " 0 ", longArc, 1699 " 0 ", x1, ",", y1 1700 ]; 1701 1702 svg += '<path class="line" d="' + dArray.join('') + '"/>'; 1703 } 1704 1705 if ( drawRadial && this.alphaLine.end ) 1706 { 1707 svg += '<line x1="' + x3 + '" y1="' + y3 + '" x2="' + x4 + '" y2="' + y4 + '"/>'; 1708 } 1709 } 1710 } 1711 else 1712 { 1713 context.lineWidth = thinLineWidth; 1714 context.strokeStyle = 'black'; 1715 context.beginPath(); 1716 context.arc(0, 0, radiusInner, angleStart, angleEnd, false); 1717 context.globalAlpha = this.alphaArc.current(); 1718 context.stroke(); 1719 1720 if ( drawRadial ) 1721 { 1722 var x1 = radiusInner * Math.cos(angleEnd); 1723 var y1 = radiusInner * Math.sin(angleEnd); 1724 var x2 = gRadius * Math.cos(angleEnd); 1725 var y2 = gRadius * Math.sin(angleEnd); 1726 1727 context.beginPath(); 1728 context.moveTo(x1, y1); 1729 context.lineTo(x2, y2); 1730 1731// if ( this.getCollapse() )//( selected && this != selectedNode ) 1732 { 1733 context.globalAlpha = this.alphaLine.current(); 1734 } 1735 1736 context.stroke(); 1737 } 1738 } 1739 } 1740 1741 this.drawMap = function(child) 1742 { 1743 if ( this.parent ) 1744 { 1745 this.parent.drawMap(child); 1746 } 1747 1748 if ( this.getCollapse() && this != child || this == focusNode ) 1749 { 1750 return; 1751 } 1752 1753 var angleStart = 1754 (child.baseMagnitude - this.baseMagnitude) / this.magnitude * Math.PI * 2 + 1755 rotationOffset; 1756 var angleEnd = 1757 (child.baseMagnitude - this.baseMagnitude + child.magnitude) / 1758 this.magnitude * Math.PI * 2 + 1759 rotationOffset; 1760 1761 var box = this.getMapPosition(); 1762 1763 context.save(); 1764 context.fillStyle = 'black'; 1765 context.textAlign = 'end'; 1766 context.textBaseline = 'middle'; 1767 1768 var textX = box.x - mapRadius - mapBuffer; 1769 var percentage = getPercentage(child.magnitude / this.magnitude); 1770 1771 var highlight = this == selectedNode || this == highlightedNode; 1772 1773 if ( highlight ) 1774 { 1775 context.font = fontBold; 1776 } 1777 else 1778 { 1779 context.font = fontNormal; 1780 } 1781 1782 context.fillText(percentage + '% of', textX, box.y - mapRadius / 3); 1783 context.fillText(this.name, textX, box.y + mapRadius / 3); 1784 1785 if ( highlight ) 1786 { 1787 context.font = fontNormal; 1788 } 1789 1790 if ( this == highlightedNode && this != selectedNode ) 1791 { 1792 context.fillStyle = 'rgb(245, 245, 245)'; 1793// context.fillStyle = 'rgb(200, 200, 200)'; 1794 } 1795 else 1796 { 1797 context.fillStyle = 'rgb(255, 255, 255)'; 1798 } 1799 1800 context.beginPath(); 1801 context.arc(box.x, box.y, mapRadius, 0, Math.PI * 2, true); 1802 context.closePath(); 1803 context.fill(); 1804 1805 if ( this == selectedNode ) 1806 { 1807 context.lineWidth = 1; 1808 context.fillStyle = 'rgb(100, 100, 100)'; 1809 } 1810 else 1811 { 1812 if ( this == highlightedNode ) 1813 { 1814 context.lineWidth = .2; 1815 context.fillStyle = 'rgb(190, 190, 190)'; 1816 } 1817 else 1818 { 1819 context.lineWidth = .2; 1820 context.fillStyle = 'rgb(200, 200, 200)'; 1821 } 1822 } 1823 1824 var maxDepth = this.getMaxDepth(); 1825 1826 if ( ! compress && maxDepth > maxPossibleDepth + this.getDepth() - 1 ) 1827 { 1828 maxDepth = maxPossibleDepth + this.getDepth() - 1; 1829 } 1830 1831 if ( this.getDepth() < selectedNode.getDepth() ) 1832 { 1833 if ( child.getDepth() - 1 >= maxDepth ) 1834 { 1835 maxDepth = child.getDepth(); 1836 } 1837 } 1838 1839 var radiusInner; 1840 1841 if ( compress ) 1842 { 1843 radiusInner = 0; 1844// Math.atan(child.getDepth() - this.getDepth()) / 1845// Math.PI * 2 * .9; 1846 } 1847 else 1848 { 1849 radiusInner = 1850 (child.getDepth() - this.getDepth()) / 1851 (maxDepth - this.getDepth() + 1); 1852 } 1853 1854 context.stroke(); 1855 context.beginPath(); 1856 1857 if ( radiusInner == 0 ) 1858 { 1859 context.moveTo(box.x, box.y); 1860 } 1861 else 1862 { 1863 context.arc(box.x, box.y, mapRadius * radiusInner, angleEnd, angleStart, true); 1864 } 1865 1866 context.arc(box.x, box.y, mapRadius, angleStart, angleEnd, false); 1867 context.closePath(); 1868 context.fill(); 1869 1870 if ( this == highlightedNode && this != selectedNode ) 1871 { 1872 context.lineWidth = 1; 1873 context.stroke(); 1874 } 1875 1876 context.restore(); 1877 } 1878 1879 this.drawReferenceRings = function(childRadiusInner) 1880 { 1881 if ( snapshotMode ) 1882 { 1883 svg += 1884 '<circle cx="' + centerX + '" cy="' + centerY + 1885 '" r="' + childRadiusInner + '"/>'; 1886 svg += 1887 '<circle cx="' + centerX + '" cy="' + centerY + 1888 '" r="' + gRadius + '"/>'; 1889 } 1890 else 1891 { 1892 context.globalAlpha = 1 - this.alphaLine.current();//this.getUncollapsed().alphaLine.current(); 1893 context.beginPath(); 1894 context.arc(0, 0, childRadiusInner, 0, Math.PI * 2, false); 1895 context.stroke(); 1896 context.beginPath(); 1897 context.arc(0, 0, gRadius, 0, Math.PI * 2, false); 1898 context.stroke(); 1899 } 1900 } 1901 1902 this.getCollapse = function() 1903 { 1904 return ( 1905 collapse && 1906 this.collapse && 1907 this.depth != maxAbsoluteDepth 1908 ); 1909 } 1910 1911 this.getDepth = function() 1912 { 1913 if ( collapse ) 1914 { 1915 return this.depthCollapsed; 1916 } 1917 else 1918 { 1919 return this.depth; 1920 } 1921 } 1922 1923 this.getMagnitude = function() 1924 { 1925 return this.attributes[magnitudeIndex][currentDataset]; 1926 } 1927 1928 this.getMapPosition = function() 1929 { 1930 return { 1931 x : (details.offsetLeft + details.clientWidth - mapRadius), 1932 y : ((focusNode.getDepth() - this.getDepth()) * 1933 (mapBuffer + mapRadius * 2) - mapRadius) + 1934 details.clientHeight + details.offsetTop 1935 }; 1936 } 1937 1938 this.getMaxDepth = function(limit) 1939 { 1940 var max; 1941 1942 if ( collapse ) 1943 { 1944 return this.maxDepthCollapsed; 1945 } 1946 else 1947 { 1948 if ( this.maxDepth > maxAbsoluteDepth ) 1949 { 1950 return maxAbsoluteDepth; 1951 } 1952 else 1953 { 1954 return this.maxDepth; 1955 } 1956 } 1957 } 1958 1959 this.getData = function(index, summary) 1960 { 1961 var files = new Array(); 1962 1963 if 1964 ( 1965 this.attributes[index] != null && 1966 this.attributes[index][currentDataset] != null && 1967 this.attributes[index][currentDataset] != '' 1968 ) 1969 { 1970 files.push 1971 ( 1972 document.location + 1973 '.files/' + 1974 this.attributes[index][currentDataset] 1975 ); 1976 } 1977 1978 if ( summary ) 1979 { 1980 for ( var i = 0; i < this.children.length; i++ ) 1981 { 1982 files = files.concat(this.children[i].getData(index, true)); 1983 } 1984 } 1985 1986 return files; 1987 } 1988 1989 this.getList = function(index, summary) 1990 { 1991 var list; 1992 1993 if 1994 ( 1995 this.attributes[index] != null && 1996 this.attributes[index][currentDataset] != null 1997 ) 1998 { 1999 list = this.attributes[index][currentDataset]; 2000 } 2001 else 2002 { 2003 list = new Array(); 2004 } 2005 2006 if ( summary ) 2007 { 2008 for ( var i = 0; i < this.children.length; i++ ) 2009 { 2010 list = list.concat(this.children[i].getList(index, true)); 2011 } 2012 } 2013 2014 return list; 2015 } 2016 2017 this.getParent = function() 2018 { 2019 // returns parent, accounting for collapsing or 0 if doesn't exist 2020 2021 var parent = this.parent; 2022 2023 while ( parent != 0 && parent.getCollapse() ) 2024 { 2025 parent = parent.parent; 2026 } 2027 2028 return parent; 2029 } 2030 2031 this.getPercentage = function() 2032 { 2033 return getPercentage(this.magnitude / selectedNode.magnitude); 2034 } 2035 2036 this.getUnclassifiedPercentage = function() 2037 { 2038 var lastChild = this.children[this.children.length - 1]; 2039 2040 return getPercentage 2041 ( 2042 ( 2043 this.baseMagnitude + 2044 this.magnitude - 2045 lastChild.magnitude - 2046 lastChild.baseMagnitude 2047 ) / this.magnitude 2048 ) + '%'; 2049 } 2050 2051 this.getUnclassifiedText = function() 2052 { 2053 return '[unassigned '+ this.name + ']'; 2054 } 2055 2056 this.getUncollapsed = function() 2057 { 2058 // recurse through collapsed children until uncollapsed node is found 2059 2060 if ( this.getCollapse() ) 2061 { 2062 return this.children[0].getUncollapsed(); 2063 } 2064 else 2065 { 2066 return this; 2067 } 2068 } 2069 2070 this.hasChildren = function() 2071 { 2072 return this.children.length && this.depth < maxAbsoluteDepth && this.magnitude; 2073 } 2074 2075 this.hasParent = function(parent) 2076 { 2077 if ( this.parent ) 2078 { 2079 if ( this.parent == parent ) 2080 { 2081 return true; 2082 } 2083 else 2084 { 2085 return this.parent.hasParent(parent); 2086 } 2087 } 2088 else 2089 { 2090 return false; 2091 } 2092 } 2093 2094 this.maxVisibleDepth = function(maxDepth) 2095 { 2096 var childInnerRadius; 2097 var depth = this.getDepth() - selectedNode.getDepth() + 1; 2098 var currentMaxDepth = depth; 2099 2100 if ( this.hasChildren() && depth < maxDepth) 2101 { 2102 var lastChild = this.children[this.children.length - 1]; 2103 2104 if ( this.name == 'Pseudomonadaceae' ) 2105 { 2106 var x = 3; 2107 } 2108 2109 if 2110 ( 2111 lastChild.baseMagnitude + lastChild.magnitude < 2112 this.baseMagnitude + this.magnitude 2113 ) 2114 { 2115 currentMaxDepth++; 2116 } 2117 2118 if ( compress ) 2119 { 2120 childInnerRadius = compressedRadii[depth - 1]; 2121 } 2122 else 2123 { 2124 childInnerRadius = (depth) / maxDepth; 2125 } 2126 2127 for ( var i = 0; i < this.children.length; i++ ) 2128 { 2129 if 2130 (//true || 2131 this.children[i].magnitude * 2132 angleFactor * 2133 (childInnerRadius + 1) * 2134 gRadius >= 2135 minWidth() 2136 ) 2137 { 2138 var childMaxDepth = this.children[i].maxVisibleDepth(maxDepth); 2139 2140 if ( childMaxDepth > currentMaxDepth ) 2141 { 2142 currentMaxDepth = childMaxDepth; 2143 } 2144 } 2145 } 2146 } 2147 2148 return currentMaxDepth; 2149 } 2150 2151 this.resetLabelWidth = function() 2152 { 2153 var nameWidthOld = this.nameWidth; 2154 2155 if ( ! this.radial )//&& fontSize != fontSizeLast ) 2156 { 2157 var dim = context.measureText(this.name); 2158 this.nameWidth = dim.width; 2159 } 2160 2161 if ( fontSize != fontSizeLast && this.labelWidth.end == nameWidthOld * labelWidthFudge ) 2162 { 2163 // font size changed; adjust start of tween to match 2164 2165 this.labelWidth.start = this.nameWidth * labelWidthFudge; 2166 } 2167 else 2168 { 2169 this.labelWidth.start = this.labelWidth.current(); 2170 } 2171 2172 this.labelWidth.end = this.nameWidth * labelWidthFudge; 2173 } 2174 2175 this.restrictLabelWidth = function(width) 2176 { 2177 if ( width < this.labelWidth.end ) 2178 { 2179 this.labelWidth.end = width; 2180 } 2181 } 2182 2183 this.search = function() 2184 { 2185 this.isSearchResult = false; 2186 this.searchResults = 0; 2187 2188 if 2189 ( 2190 ! this.getCollapse() && 2191 search.value != '' && 2192 this.name.toLowerCase().indexOf(search.value.toLowerCase()) != -1 2193 ) 2194 { 2195 this.isSearchResult = true; 2196 this.searchResults = 1; 2197 nSearchResults++; 2198 } 2199 2200 for ( var i = 0; i < this.children.length; i++ ) 2201 { 2202 this.searchResults += this.children[i].search(); 2203 } 2204 2205 return this.searchResults; 2206 } 2207 2208 this.searchResultChildren = function() 2209 { 2210 if ( this.isSearchResult ) 2211 { 2212 return this.searchResults - 1; 2213 } 2214 else 2215 { 2216 return this.searchResults; 2217 } 2218 } 2219 2220 this.setDepth = function(depth, depthCollapsed) 2221 { 2222 this.depth = depth; 2223 this.depthCollapsed = depthCollapsed; 2224 2225 if 2226 ( 2227 this.children.length == 1 && 2228// this.magnitude > 0 && 2229 this.children[0].magnitude == this.magnitude && 2230 ( head.children.length > 1 || this.children[0].children.length ) 2231 ) 2232 { 2233 this.collapse = true; 2234 } 2235 else 2236 { 2237 this.collapse = false; 2238 depthCollapsed++; 2239 } 2240 2241 for ( var i = 0; i < this.children.length; i++ ) 2242 { 2243 this.children[i].setDepth(depth + 1, depthCollapsed); 2244 } 2245 } 2246 2247 this.setHighlightStyle = function() 2248 { 2249 context.lineWidth = highlightLineWidth; 2250 2251 if ( this.hasChi…
Large files files are truncated, but you can click here to view the full file