PageRenderTime 153ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/Tutorial/example_html/src/krona-2.0.js

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

Large files files are truncated, but you can click here to view the full file