PageRenderTime 80ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  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.hasChildren() || this != focusNode || this != highlightedNode )
  1952. {
  1953. context.strokeStyle = 'black';
  1954. context.fillStyle = "rgba(255, 255, 255, .3)";
  1955. }
  1956. else
  1957. {
  1958. context.strokeStyle = 'rgb(90,90,90)';
  1959. context.fillStyle = "rgba(155, 155, 155, .3)";
  1960. }
  1961. }
  1962. this.setLabelWidth = function(node)
  1963. {
  1964. if ( ! shorten || this.radial )
  1965. {
  1966. return; // don't need to set width
  1967. }
  1968. if ( node.hide )
  1969. {
  1970. alert('wtf');
  1971. return;
  1972. }
  1973. var angle = (this.angleStart.end + this.angleEnd.end) / 2;
  1974. var a; // angle difference
  1975. if ( node == selectedNode )
  1976. {
  1977. a = Math.abs(angle - node.angleOther);
  1978. }
  1979. else
  1980. {
  1981. a = Math.abs(angle - (node.angleStart.end + node.angleEnd.end) / 2);
  1982. }
  1983. if ( a == 0 )
  1984. {
  1985. return;
  1986. }
  1987. if ( a > Math.PI )
  1988. {
  1989. a = 2 * Math.PI - a;
  1990. }
  1991. if ( node.radial || node == selectedNode )
  1992. {
  1993. var nodeLabelRadius;
  1994. if ( node == selectedNode )
  1995. {
  1996. // radial 'other' label
  1997. nodeLabelRadius = (node.children[0].radiusInner.end + 1) / 2;
  1998. }
  1999. else
  2000. {
  2001. nodeLabelRadius = (node.radiusInner.end + 1) / 2;
  2002. }
  2003. if ( a < Math.PI / 2 )
  2004. {
  2005. var r = this.labelRadius.end * gRadius + .5 * fontSize
  2006. var hypotenuse = r / Math.cos(a);
  2007. var opposite = r * Math.tan(a);
  2008. var fontRadius = .8 * fontSize;
  2009. if
  2010. (
  2011. nodeLabelRadius * gRadius < hypotenuse &&
  2012. this.labelWidth.end / 2 + fontRadius > opposite
  2013. )
  2014. {
  2015. this.labelWidth.end = 2 * (opposite - fontRadius);
  2016. }
  2017. }
  2018. }
  2019. else if
  2020. (
  2021. this.labelRadius.end == node.labelRadius.end &&
  2022. a < Math.PI / 4
  2023. )
  2024. {
  2025. // same radius with small angle; use circumferential approximation
  2026. var dist = a * this.labelRadius.end * gRadius - fontSize * (1 - a * 4 / Math.PI) * 1.3;
  2027. if ( this.labelWidth.end < dist )
  2028. {
  2029. node.restrictLabelWidth((dist - this.labelWidth.end / 2) * 2);
  2030. }
  2031. else if ( node.labelWidth.end < dist )
  2032. {
  2033. this.restrictLabelWidth((dist - node.labelWidth.end / 2) * 2);
  2034. }
  2035. else
  2036. {
  2037. // both labels reach halfway point; restrict both
  2038. this.labelWidth.end = dist;
  2039. node.labelWidth.end = dist
  2040. }
  2041. }
  2042. else
  2043. {
  2044. var r1 = this.labelRadius.end * gRadius;
  2045. var r2 = node.labelRadius.end * gRadius;
  2046. // first adjust the radii to account for the height of the font by shifting them
  2047. // toward each other
  2048. //
  2049. var fontFudge = .35 * fontSize;
  2050. //
  2051. if ( this.labelRadius.end < node.labelRadius.end )
  2052. {
  2053. r1 += fontFudge;
  2054. r2 -= fontFudge;
  2055. }
  2056. else if ( this.labelRadius.end > node.labelRadius.end )
  2057. {
  2058. r1 -= fontFudge;
  2059. r2 += fontFudge;
  2060. }
  2061. else
  2062. {
  2063. r1 -= fontFudge;
  2064. r2 -= fontFudge;
  2065. }
  2066. var r1s = r1 * r1;
  2067. var r2s = r2 * r2;
  2068. // distance between the centers of the two labels
  2069. //
  2070. var dist = Math.sqrt(r1s + r2s - 2 * r1 * r2 * Math.cos(a));
  2071. // angle at our label center between our radius and the line to the other label center
  2072. //
  2073. var b = Math.acos((r1s + dist * dist - r2s) / (2 * r1 * dist));
  2074. // distance from our label center to the intersection of the two tangents
  2075. //
  2076. var l1 = Math.sin(a + b - Math.PI / 2) * dist / Math.sin(Math.PI - a);
  2077. // distance from other label center the the intersection of the two tangents
  2078. //
  2079. var l2 = Math.sin(Math.PI / 2 - b) * dist / Math.sin(Math.PI - a);
  2080. l1 = Math.abs(l1) - .4 * fontSize;
  2081. l2 = Math.abs(l2) - .4 * fontSize;
  2082. /*
  2083. // amount to shorten the distances because of the height of the font
  2084. //
  2085. var l3 = 0;
  2086. var fontRadius = fontSize * .55;
  2087. //
  2088. if ( l1 < 0 || l2 < 0 )
  2089. {
  2090. var l4 = fontRadius / Math.tan(a);
  2091. l1 = Math.abs(l1);
  2092. l2 = Math.abs(l2);
  2093. l1 -= l4;
  2094. l2 -= l4;
  2095. }
  2096. else
  2097. {
  2098. var c = Math.PI - a;
  2099. l3 = fontRadius * Math.tan(c / 2);
  2100. }
  2101. */
  2102. if ( this.labelWidth.end / 2 > l1 && node.labelWidth.end / 2 > l2 )
  2103. {
  2104. // shorten the farthest one from the intersection
  2105. if ( l1 > l2 )
  2106. {
  2107. this.restrictLabelWidth(2 * (l1));// - l3 - fontRadius));
  2108. }
  2109. else
  2110. {
  2111. node.restrictLabelWidth(2 * (l2));// - l3 - fontRadius));
  2112. }
  2113. }/*
  2114. else if ( this.labelWidth.end / 2 > l1 + l3 && node.labelWidth.end / 2 > l2 - l3 )
  2115. {
  2116. node.restrictLabelWidth(2 * (l2 - l3));
  2117. }
  2118. else if ( this.labelWidth.end / 2 > l1 - l3 && node.labelWidth.end / 2 > l2 + l3 )
  2119. {
  2120. this.restrictLabelWidth(2 * (l1 - l3));
  2121. }*/
  2122. }
  2123. }
  2124. this.setMagnitudes = function(baseMagnitude)
  2125. {
  2126. this.magnitude = this.getMagnitude();
  2127. this.baseMagnitude = baseMagnitude;
  2128. for ( var i = 0; i < this.children.length; i++ )
  2129. {
  2130. this.children[i].setMagnitudes(baseMagnitude);
  2131. baseMagnitude += this.children[i].magnitude;
  2132. }
  2133. this.maxChildMagnitude = baseMagnitude;
  2134. }
  2135. this.setMaxDepths = function()
  2136. {
  2137. this.maxDepth = this.depth;
  2138. this.maxDepthCollapsed = this.depthCollapsed;
  2139. for ( i in this.children )
  2140. {
  2141. var child = this.children[i];
  2142. child.setMaxDepths();
  2143. if ( child.maxDepth > this.maxDepth )
  2144. {
  2145. this.maxDepth = child.maxDepth;
  2146. }
  2147. if
  2148. (
  2149. child.maxDepthCollapsed > this.maxDepthCollapsed &&
  2150. (child.depth <= maxAbsoluteDepth || maxAbsoluteDepth == 0)
  2151. )
  2152. {
  2153. this.maxDepthCollapsed = child.maxDepthCollapsed;
  2154. }
  2155. }
  2156. }
  2157. this.setTargetLabelRadius = function()
  2158. {
  2159. var depth = this.getDepth() - selectedNode.getDepth() + 1;
  2160. var index = depth - 2;
  2161. var labelOffset = labelOffsets[index];
  2162. if ( this.radial )
  2163. {
  2164. //this.labelRadius.setTarget((this.radiusInner.end + 1) / 2);
  2165. var max =
  2166. depth == maxDisplayDepth ?
  2167. 1 :
  2168. compressedRadii[index + 1];
  2169. this.labelRadius.setTarget((compressedRadii[index] + max) / 2);
  2170. }
  2171. else
  2172. {
  2173. var radiusCenter;
  2174. var width;
  2175. if ( compress )
  2176. {
  2177. if ( nLabelOffsets[index] > 1 )
  2178. {
  2179. this.labelRadius.setTarget
  2180. (
  2181. lerp
  2182. (
  2183. labelOffset + .75,
  2184. 0,
  2185. nLabelOffsets[index] + .5,
  2186. compressedRadii[index],
  2187. compressedRadii[index + 1]
  2188. )
  2189. );
  2190. }
  2191. else
  2192. {
  2193. this.labelRadius.setTarget((compressedRadii[index] + compressedRadii[index + 1]) / 2);
  2194. }
  2195. }
  2196. else
  2197. {
  2198. radiusCenter =
  2199. nodeRadius * (depth - 1) +
  2200. nodeRadius / 2;
  2201. width = nodeRadius;
  2202. this.labelRadius.setTarget
  2203. (
  2204. radiusCenter + width * ((labelOffset + 1) / (nLabelOffsets[index] + 1) - .5)
  2205. );
  2206. }
  2207. }
  2208. if ( ! this.hide && ! this.keyed && nLabelOffsets[index] )
  2209. {
  2210. // check last and first labels in each track for overlap
  2211. for ( var i = 0; i < maxDisplayDepth - 1; i++ )
  2212. {
  2213. for ( var j = 0; j <= nLabelOffsets[i]; j++ )
  2214. {
  2215. var last = labelLastNodes[i][j];
  2216. var first = labelFirstNodes[i][j];
  2217. if ( last )
  2218. {
  2219. if ( j == nLabelOffsets[i] )
  2220. {
  2221. // last is radial
  2222. this.setLabelWidth(last);
  2223. }
  2224. else
  2225. {
  2226. last.setLabelWidth(this);
  2227. }
  2228. }
  2229. if ( first )
  2230. {
  2231. if ( j == nLabelOffsets[i] )
  2232. {
  2233. this.setLabelWidth(first);
  2234. }
  2235. else
  2236. {
  2237. first.setLabelWidth(this);
  2238. }
  2239. }
  2240. }
  2241. }
  2242. if ( selectedNode.canDisplayLabelOther )
  2243. {
  2244. this.setLabelWidth(selectedNode); // in case there is an 'other' label
  2245. }
  2246. if ( this.radial )
  2247. {
  2248. // use the last 'track' of this depth for radial
  2249. labelLastNodes[index][nLabelOffsets[index]] = this;
  2250. if ( labelFirstNodes[index][nLabelOffsets[index]] == 0 )
  2251. {
  2252. labelFirstNodes[index][nLabelOffsets[index]] = this;
  2253. }
  2254. }
  2255. else
  2256. {
  2257. labelLastNodes[index][labelOffset] = this;
  2258. // update offset
  2259. labelOffsets[index] += 1;
  2260. if ( labelOffsets[index] > nLabelOffsets[index] )
  2261. {
  2262. labelOffsets[index] -= nLabelOffsets[index];
  2263. if ( !(nLabelOffsets[index] & 1) )
  2264. {
  2265. labelOffsets[index]--;
  2266. }
  2267. }
  2268. else if ( labelOffsets[index] == nLabelOffsets[index] )
  2269. {
  2270. labelOffsets[index] -= nLabelOffsets[index];
  2271. if ( false && !(nLabelOffsets[index] & 1) )
  2272. {
  2273. labelOffsets[index]++;
  2274. }
  2275. }
  2276. if ( labelFirstNodes[index][labelOffset] == 0 )
  2277. {
  2278. labelFirstNodes[index][labelOffset] = this;
  2279. }
  2280. }
  2281. }
  2282. else if ( this.hide )
  2283. {
  2284. this.labelWidth.end = 0;
  2285. }
  2286. }
  2287. this.setTargets = function()
  2288. {
  2289. if ( this == selectedNode )
  2290. {
  2291. this.setTargetsSelected
  2292. (
  2293. 0,
  2294. 1,
  2295. lightnessBase,
  2296. false,
  2297. false
  2298. );
  2299. return;
  2300. }
  2301. var depthRelative = this.getDepth() - selectedNode.getDepth();
  2302. var parentOfSelected = selectedNode.hasParent(this);
  2303. /* (
  2304. // ! this.getCollapse() &&
  2305. this.baseMagnitude <= selectedNode.baseMagnitude &&
  2306. this.baseMagnitude + this.magnitude >=
  2307. selectedNode.baseMagnitude + selectedNode.magnitude
  2308. );
  2309. */
  2310. if ( parentOfSelected )
  2311. {
  2312. this.resetLabelWidth();
  2313. }
  2314. else
  2315. {
  2316. //context.font = fontNormal;
  2317. var dim = context.measureText(this.name);
  2318. this.nameWidth = dim.width;
  2319. //this.labelWidth.setTarget(this.labelWidth.end);
  2320. this.labelWidth.setTarget(0);
  2321. }
  2322. // set angles
  2323. //
  2324. if ( this.baseMagnitude <= selectedNode.baseMagnitude )
  2325. {
  2326. this.angleStart.setTarget(0);
  2327. }
  2328. else
  2329. {
  2330. this.angleStart.setTarget(Math.PI * 2);
  2331. }
  2332. //
  2333. if
  2334. (
  2335. parentOfSelected ||
  2336. this.baseMagnitude + this.magnitude >=
  2337. selectedNode.baseMagnitude + selectedNode.magnitude
  2338. )
  2339. {
  2340. this.angleEnd.setTarget(Math.PI * 2);
  2341. }
  2342. else
  2343. {
  2344. this.angleEnd.setTarget(0);
  2345. }
  2346. // children
  2347. //
  2348. for ( var i = 0; i < this.children.length; i++ )
  2349. {
  2350. this.children[i].setTargets();
  2351. }
  2352. if ( this.getDepth() <= selectedNode.getDepth() )
  2353. {
  2354. // collapse in
  2355. this.radiusInner.setTarget(0);
  2356. if ( parentOfSelected )
  2357. {
  2358. this.labelRadius.setTarget
  2359. (
  2360. (depthRelative) *
  2361. historySpacingFactor * fontSize / gRadius
  2362. );
  2363. //this.scale.setTarget(1 - (selectedNode.getDepth() - this.getDepth()) / 18); // TEMP
  2364. }
  2365. else
  2366. {
  2367. this.labelRadius.setTarget(0);
  2368. //this.scale.setTarget(1); // TEMP
  2369. }
  2370. }
  2371. else if ( depthRelative + 1 > maxDisplayDepth )
  2372. {
  2373. // collapse out
  2374. this.radiusInner.setTarget(1);
  2375. this.labelRadius.setTarget(1);
  2376. //this.scale.setTarget(1); // TEMP
  2377. }
  2378. else
  2379. {
  2380. // don't collapse
  2381. if ( compress )
  2382. {
  2383. this.radiusInner.setTarget(compressedRadii[depthRelative - 1]);
  2384. }
  2385. else
  2386. {
  2387. this.radiusInner.setTarget(nodeRadius * (depthRelative));
  2388. }
  2389. //this.scale.setTarget(1); // TEMP
  2390. if ( this == selectedNode )
  2391. {
  2392. this.labelRadius.setTarget(0);
  2393. }
  2394. else
  2395. {
  2396. if ( compress )
  2397. {
  2398. this.labelRadius.setTarget
  2399. (
  2400. (compressedRadii[depthRelative - 1] + compressedRadii[depthRelative]) / 2
  2401. );
  2402. }
  2403. else
  2404. {
  2405. this.labelRadius.setTarget(nodeRadius * (depthRelative) + nodeRadius / 2);
  2406. }
  2407. }
  2408. }
  2409. // this.r.start = this.r.end;
  2410. // this.g.start = this.g.end;
  2411. // this.b.start = this.b.end;
  2412. this.r.setTarget(255);
  2413. this.g.setTarget(255);
  2414. this.b.setTarget(255);
  2415. this.alphaLine.setTarget(0);
  2416. this.alphaArc.setTarget(0);
  2417. this.alphaWedge.setTarget(0);
  2418. this.alphaPattern.setTarget(0);
  2419. this.alphaOther.setTarget(0);
  2420. if ( parentOfSelected && ! this.getCollapse() )
  2421. {
  2422. var alpha =
  2423. (
  2424. 1 -
  2425. (selectedNode.getDepth() - this.getDepth()) /
  2426. (Math.floor((compress ? compressedRadii[0] : nodeRadius) * gRadius / (historySpacingFactor * fontSize) - .5) + 1)
  2427. );
  2428. if ( alpha < 0 )
  2429. {
  2430. alpha = 0;
  2431. }
  2432. this.alphaLabel.setTarget(alpha);
  2433. this.radial = false;
  2434. }
  2435. else
  2436. {
  2437. this.alphaLabel.setTarget(0);
  2438. }
  2439. this.hideAlonePrev = this.hideAlone;
  2440. this.hidePrev = this.hide;
  2441. if ( parentOfSelected )
  2442. {
  2443. this.hideAlone = false;
  2444. this.hide = false;
  2445. }
  2446. if ( this.getParent() == selectedNode.getParent() )
  2447. {
  2448. this.hiddenEnd = null;
  2449. }
  2450. this.radialPrev = this.radial;
  2451. }
  2452. this.setTargetsSelected = function(hueMin, hueMax, lightness, hide, nextSiblingHidden)
  2453. {
  2454. var collapse = this.getCollapse();
  2455. var depth = this.getDepth() - selectedNode.getDepth() + 1;
  2456. var canDisplayChildLabels = false;
  2457. var lastChild;
  2458. if ( this.hasChildren() )//&& ! hide )
  2459. {
  2460. lastChild = this.children[this.children.length - 1];
  2461. this.hideAlone = true;
  2462. }
  2463. else
  2464. {
  2465. this.hideAlone = false;
  2466. }
  2467. // set child wedges
  2468. //
  2469. for ( var i = 0; i < this.children.length; i++ )
  2470. {
  2471. this.children[i].setTargetWedge();
  2472. if
  2473. (
  2474. ! this.children[i].hide &&
  2475. ( collapse || depth < maxDisplayDepth ) &&
  2476. this.depth < maxAbsoluteDepth
  2477. )
  2478. {
  2479. canDisplayChildLabels = true;
  2480. this.hideAlone = false;
  2481. }
  2482. }
  2483. if ( this == selectedNode || lastChild && lastChild.angleEnd.end < this.angleEnd.end - .01)
  2484. {
  2485. this.hideAlone = false;
  2486. }
  2487. if ( this.hideAlonePrev == undefined )
  2488. {
  2489. this.hideAlonePrev = this.hideAlone;
  2490. }
  2491. if ( this == selectedNode )
  2492. {
  2493. var otherArc =
  2494. angleFactor *
  2495. (
  2496. this.baseMagnitude + this.magnitude -
  2497. lastChild.baseMagnitude - lastChild.magnitude
  2498. );
  2499. this.canDisplayLabelOther =
  2500. otherArc *
  2501. (this.children[0].radiusInner.end + 1) * gRadius >=
  2502. minWidth();
  2503. this.keyUnclassified = false;
  2504. if ( this.canDisplayLabelOther )
  2505. {
  2506. this.angleOther = Math.PI * 2 - otherArc / 2;
  2507. }
  2508. else if ( otherArc > 0.0000000001 )
  2509. {
  2510. this.keyUnclassified = true;
  2511. keys++;
  2512. }
  2513. this.angleStart.setTarget(0);
  2514. this.angleEnd.setTarget(Math.PI * 2);
  2515. this.radiusInner.setTarget(0);
  2516. this.hidePrev = this.hide;
  2517. this.hide = false;
  2518. this.hideAlonePrev = this.hideAlone;
  2519. this.hideAlone = false;
  2520. this.keyed = false;
  2521. }
  2522. if ( hueMax - hueMin > 1 / 12 )
  2523. {
  2524. hueMax = hueMin + 1 / 12;
  2525. }
  2526. // set lightness
  2527. //
  2528. if ( ! ( hide || this.hideAlone ) )
  2529. {
  2530. if ( useHue() )
  2531. {
  2532. lightness = (lightnessBase + lightnessMax) / 2;
  2533. }
  2534. else
  2535. {
  2536. lightness = lightnessBase + (depth - 1) * lightnessFactor;
  2537. if ( lightness > lightnessMax )
  2538. {
  2539. lightness = lightnessMax;
  2540. }
  2541. }
  2542. }
  2543. if ( hide )
  2544. {
  2545. this.hide = true;
  2546. }
  2547. if ( this.hidePrev == undefined )
  2548. {
  2549. this.hidePrev = this.hide;
  2550. }
  2551. var hiddenStart = -1;
  2552. var hiddenHueNumer = 0;
  2553. var hiddenHueDenom = 0;
  2554. var i = 0;
  2555. if ( ! this.hide )
  2556. {
  2557. this.hiddenEnd = null;
  2558. }
  2559. while ( true )
  2560. {
  2561. if ( ! this.hideAlone && ! hide && ( i == this.children.length || ! this.children[i].hide ) )
  2562. {
  2563. // reached a non-hidden child or the end; set targets for
  2564. // previous group of hidden children (if any) using their
  2565. // average hue
  2566. if ( hiddenStart != -1 )
  2567. {
  2568. var hiddenHue = hiddenHueDenom ? hiddenHueNumer / hiddenHueDenom : hueMin;
  2569. for ( var j = hiddenStart; j < i; j++ )
  2570. {
  2571. this.children[j].setTargetsSelected
  2572. (
  2573. hiddenHue,
  2574. null,
  2575. lightness,
  2576. false,
  2577. j < i - 1
  2578. );
  2579. this.children[j].hiddenEnd = null;
  2580. }
  2581. this.children[hiddenStart].hiddenEnd = i - 1;
  2582. }
  2583. }
  2584. if ( i == this.children.length )
  2585. {
  2586. break;
  2587. }
  2588. var child = this.children[i];
  2589. var childHueMin;
  2590. var childHueMax;
  2591. if ( this.magnitude > 0 && ! this.hide && ! this.hideAlone )
  2592. {
  2593. if ( useHue() )
  2594. {
  2595. childHueMin = child.hues[currentDataset];
  2596. }
  2597. else if ( this == selectedNode )
  2598. {
  2599. var min = 0.0;
  2600. var max = 1.0;
  2601. if ( this.children.length > 6 )
  2602. {
  2603. childHueMin = lerp((1 - Math.pow(1 - i / this.children.length, 1.4)) * .95, 0, 1, min, max);
  2604. childHueMax = lerp((1 - Math.pow(1 - (i + .55) / this.children.length, 1.4)) * .95, 0, 1, min, max);
  2605. }
  2606. else
  2607. {
  2608. childHueMin = lerp(i / this.children.length, 0, 1, min, max);
  2609. childHueMax = lerp((i + .55) / this.children.length, 0, 1, min, max);
  2610. }
  2611. }
  2612. else
  2613. {
  2614. childHueMin = lerp
  2615. (
  2616. child.baseMagnitude,
  2617. this.baseMagnitude,
  2618. this.baseMagnitude + this.magnitude,
  2619. hueMin,
  2620. hueMax
  2621. );
  2622. childHueMax = lerp
  2623. (
  2624. child.baseMagnitude + child.magnitude * .99,
  2625. this.baseMagnitude,
  2626. this.baseMagnitude + this.magnitude,
  2627. hueMin,
  2628. hueMax
  2629. );
  2630. }
  2631. }
  2632. else
  2633. {
  2634. childHueMin = hueMin;
  2635. childHueMax = hueMax;
  2636. }
  2637. if ( ! this.hideAlone && ! hide && ! this.hide && child.hide )
  2638. {
  2639. if ( hiddenStart == -1 )
  2640. {
  2641. hiddenStart = i;
  2642. }
  2643. if ( useHue() )
  2644. {
  2645. hiddenHueNumer += childHueMin * child.magnitude;
  2646. hiddenHueDenom += child.magnitude;
  2647. }
  2648. else
  2649. {
  2650. hiddenHueNumer += childHueMin;
  2651. hiddenHueDenom++;
  2652. }
  2653. }
  2654. else
  2655. {
  2656. hiddenStart = -1;
  2657. this.children[i].setTargetsSelected
  2658. (
  2659. childHueMin,
  2660. childHueMax,
  2661. lightness,
  2662. hide || this.keyed || this.hideAlone || this.hide && ! collapse,
  2663. false
  2664. );
  2665. }
  2666. i++;
  2667. }
  2668. if ( this.hue && this.magnitude )
  2669. {
  2670. this.hue.setTarget(this.hues[currentDataset]);
  2671. if ( this.attributes[magnitudeIndex][lastDataset] == 0 )
  2672. {
  2673. this.hue.start = this.hue.end;
  2674. }
  2675. }
  2676. this.radialPrev = this.radial;
  2677. if ( this == selectedNode )
  2678. {
  2679. this.resetLabelWidth();
  2680. this.labelWidth.setTarget(this.nameWidth * labelWidthFudge);
  2681. this.alphaWedge.setTarget(0);
  2682. this.alphaLabel.setTarget(1);
  2683. this.alphaOther.setTarget(1);
  2684. this.alphaArc.setTarget(0);
  2685. this.alphaLine.setTarget(0);
  2686. this.alphaPattern.setTarget(0);
  2687. this.r.setTarget(255);
  2688. this.g.setTarget(255);
  2689. this.b.setTarget(255);
  2690. this.radial = false;
  2691. this.labelRadius.setTarget(0);
  2692. }
  2693. else
  2694. {
  2695. var rgb = hslToRgb
  2696. (
  2697. hueMin,
  2698. saturation,
  2699. lightness
  2700. );
  2701. this.r.setTarget(rgb.r);
  2702. this.g.setTarget(rgb.g);
  2703. this.b.setTarget(rgb.b);
  2704. this.alphaOther.setTarget(0);
  2705. this.alphaWedge.setTarget(1);
  2706. if ( this.hide || this.hideAlone )
  2707. {
  2708. this.alphaPattern.setTarget(1);
  2709. }
  2710. else
  2711. {
  2712. this.alphaPattern.setTarget(0);
  2713. }
  2714. // set radial
  2715. //
  2716. if ( ! ( hide || this.hide ) )//&& ! this.keyed )
  2717. {
  2718. if ( this.hideAlone )
  2719. {
  2720. this.radial = true;
  2721. }
  2722. else if ( false && canDisplayChildLabels )
  2723. {
  2724. this.radial = false;
  2725. }
  2726. else
  2727. {
  2728. this.radial = true;
  2729. if ( this.hasChildren() && depth < maxDisplayDepth )
  2730. {
  2731. var lastChild = this.children[this.children.length - 1];
  2732. if
  2733. (
  2734. lastChild.angleEnd.end == this.angleEnd.end ||
  2735. (
  2736. (this.angleStart.end + this.angleEnd.end) / 2 -
  2737. lastChild.angleEnd.end
  2738. ) * (this.radiusInner.end + 1) * gRadius * 2 <
  2739. minWidth()
  2740. )
  2741. {
  2742. this.radial = false;
  2743. }
  2744. }
  2745. }
  2746. }
  2747. // set alphaLabel
  2748. //
  2749. if
  2750. (
  2751. collapse ||
  2752. hide ||
  2753. this.hide ||
  2754. this.keyed ||
  2755. depth > maxDisplayDepth ||
  2756. ! this.canDisplayDepth()
  2757. )
  2758. {
  2759. this.alphaLabel.setTarget(0);
  2760. }
  2761. else
  2762. {
  2763. if
  2764. (
  2765. (this.radial || nLabelOffsets[depth - 2])
  2766. )
  2767. {
  2768. this.alphaLabel.setTarget(1);
  2769. }
  2770. else
  2771. {
  2772. this.alphaLabel.setTarget(0);
  2773. if ( this.radialPrev )
  2774. {
  2775. this.alphaLabel.start = 0;
  2776. }
  2777. }
  2778. }
  2779. // set alphaArc
  2780. //
  2781. if
  2782. (
  2783. collapse ||
  2784. hide ||
  2785. depth > maxDisplayDepth ||
  2786. ! this.canDisplayDepth()
  2787. )
  2788. {
  2789. this.alphaArc.setTarget(0);
  2790. }
  2791. else
  2792. {
  2793. this.alphaArc.setTarget(1);
  2794. }
  2795. // set alphaLine
  2796. //
  2797. if
  2798. (
  2799. hide ||
  2800. this.hide && nextSiblingHidden ||
  2801. depth > maxDisplayDepth ||
  2802. ! this.canDisplayDepth()
  2803. )
  2804. {
  2805. this.alphaLine.setTarget(0);
  2806. }
  2807. else
  2808. {
  2809. this.alphaLine.setTarget(1);
  2810. }
  2811. //if ( ! this.radial )
  2812. {
  2813. this.resetLabelWidth();
  2814. }
  2815. // set labelRadius target
  2816. //
  2817. if ( collapse )
  2818. {
  2819. this.labelRadius.setTarget(this.radiusInner.end);
  2820. }
  2821. else
  2822. {
  2823. if ( depth > maxDisplayDepth || ! this.canDisplayDepth() )
  2824. {
  2825. this.labelRadius.setTarget(1);
  2826. }
  2827. else
  2828. {
  2829. this.setTargetLabelRadius();
  2830. }
  2831. }
  2832. }
  2833. }
  2834. this.setTargetWedge = function()
  2835. {
  2836. var depth = this.getDepth() - selectedNode.getDepth() + 1;
  2837. // set angles
  2838. //
  2839. var baseMagnitudeRelative = this.baseMagnitude - selectedNode.baseMagnitude;
  2840. //
  2841. this.angleStart.setTarget(baseMagnitudeRelative * angleFactor);
  2842. this.angleEnd.setTarget((baseMagnitudeRelative + this.magnitude) * angleFactor);
  2843. // set radiusInner
  2844. //
  2845. if ( depth > maxDisplayDepth || ! this.canDisplayDepth() )
  2846. {
  2847. this.radiusInner.setTarget(1);
  2848. }
  2849. else
  2850. {
  2851. if ( compress )
  2852. {
  2853. this.radiusInner.setTarget(compressedRadii[depth - 2]);
  2854. }
  2855. else
  2856. {
  2857. this.radiusInner.setTarget(nodeRadius * (depth - 1));
  2858. }
  2859. }
  2860. if ( this.hide != undefined )
  2861. {
  2862. this.hidePrev = this.hide;
  2863. }
  2864. if ( this.hideAlone != undefined )
  2865. {
  2866. this.hideAlonePrev = this.hideAlone;
  2867. }
  2868. // set hide
  2869. //
  2870. if
  2871. (
  2872. (this.angleEnd.end - this.angleStart.end) *
  2873. (this.radiusInner.end * gRadius + gRadius) <
  2874. minWidth()
  2875. )
  2876. {
  2877. if ( depth == 2 && ! this.getCollapse() && this.depth <= maxAbsoluteDepth )
  2878. {
  2879. this.keyed = true;
  2880. keys++;
  2881. this.hide = false;
  2882. var percentage = this.getPercentage();
  2883. this.keyLabel = this.name + ' ' + percentage + '%';
  2884. var dim = context.measureText(this.keyLabel);
  2885. this.keyNameWidth = dim.width;
  2886. }
  2887. else
  2888. {
  2889. this.keyed = false;
  2890. this.hide = depth > 2;
  2891. }
  2892. }
  2893. else
  2894. {
  2895. this.keyed = false;
  2896. this.hide = false;
  2897. }
  2898. }
  2899. this.shortenLabel = function()
  2900. {
  2901. var label = this.name;
  2902. var labelWidth = this.nameWidth;
  2903. var maxWidth = this.labelWidth.current();
  2904. var minEndLength = 0;
  2905. if ( labelWidth > maxWidth && label.length > minEndLength * 2 )
  2906. {
  2907. var endLength =
  2908. Math.floor((label.length - 1) * maxWidth / labelWidth / 2);
  2909. if ( endLength < minEndLength )
  2910. {
  2911. endLength = minEndLength;
  2912. }
  2913. return (
  2914. label.substring(0, endLength) +
  2915. '...' +
  2916. label.substring(label.length - endLength));
  2917. }
  2918. else
  2919. {
  2920. return label;
  2921. }
  2922. }
  2923. /* this.shouldAddSearchResultsString = function()
  2924. {
  2925. if ( this.isSearchResult )
  2926. {
  2927. return this.searchResults > 1;
  2928. }
  2929. else
  2930. {
  2931. return this.searchResults > 0;
  2932. }
  2933. }
  2934. */
  2935. this.sort = function()
  2936. {
  2937. this.children.sort(function(a, b){return b.getMagnitude() - a.getMagnitude()});
  2938. for (var i = 0; i < this.children.length; i++)
  2939. {
  2940. this.children[i].sort();
  2941. }
  2942. }
  2943. }
  2944. function addOptionElement(position, innerHTML, title)
  2945. {
  2946. var div = document.createElement("div");
  2947. div.style.position = 'absolute';
  2948. div.style.top = position + 'px';
  2949. div.innerHTML = innerHTML;
  2950. if ( title )
  2951. {
  2952. div.title = title;
  2953. }
  2954. document.body.insertBefore(div, canvas);
  2955. return position + div.clientHeight;
  2956. }
  2957. function addOptionElements(hueName, hueDefault)
  2958. {
  2959. document.body.style.font = '11px sans-serif';
  2960. var position = 5;
  2961. details = document.createElement('div');
  2962. details.style.position = 'absolute';
  2963. details.style.top = '1%';
  2964. details.style.right = '2%';
  2965. details.style.textAlign = 'right';
  2966. document.body.insertBefore(details, canvas);
  2967. // <div id="details" style="position:absolute;top:1%;right:2%;text-align:right;">
  2968. details.innerHTML = '\
  2969. <span id="detailsName" style="font-weight:bold"></span>&nbsp;\
  2970. <input type="button" id="detailsExpand" onclick="expand(focusNode);"\
  2971. value="&harr;" title="Expand this wedge to become the new focus of the chart"/><br/>\
  2972. <div id="detailsInfo" style="float:right"></div>';
  2973. keyControl = document.createElement('input');
  2974. keyControl.type = 'button';
  2975. keyControl.value = showKeys ? 'x' : 'â&#x20AC;?';
  2976. keyControl.style.position = '';
  2977. keyControl.style.position = 'fixed';
  2978. keyControl.style.visibility = 'hidden';
  2979. document.body.insertBefore(keyControl, canvas);
  2980. // document.getElementById('options').style.fontSize = '9pt';
  2981. position = addOptionElement
  2982. (
  2983. position,
  2984. '&nbsp;<input type="button" id="back" value="&larr;" title="Go back (Shortcut: &larr;)"/>\
  2985. <input type="button" id="forward" value="&rarr;" title="Go forward (Shortcut: &rarr;)"/> \
  2986. &nbsp;Search: <input type="text" id="search"/>\
  2987. <input type="button" value="x" onclick="clearSearch()"/> \
  2988. <span id="searchResults"></span>'
  2989. );
  2990. if ( datasets > 1 )
  2991. {
  2992. var size = datasets < datasetSelectSize ? datasets : datasetSelectSize;
  2993. var select =
  2994. '<div style="float:left">&nbsp;</div><div style="float:left">' +
  2995. '<select id="datasets" style="min-width:100px" size="' + size + '" onchange="onDatasetChange()">';
  2996. for ( var i = 0; i < datasetNames.length; i++ )
  2997. {
  2998. select += '<option>' + datasetNames[i] + '</option>';
  2999. }
  3000. select +=
  3001. '</select></div>' +
  3002. '<input title="Previous dataset (Shortcut: &uarr;)" id="prevDataset" type="button" value="&uarr;" onclick="prevDataset()" disabled="true"/>' +
  3003. '<input title="Switch to the last dataset that was viewed (Shortcut: TAB)" id="lastDataset" type="button" style="font:11px Times new roman" value="last" onclick="selectLastDataset()"/>' +
  3004. '<br/><input title="Next dataset (Shortcut: &darr;)" id="nextDataset" type="button" value="&darr;" onclick="nextDataset()"/><br/>';
  3005. position = addOptionElement(position + 5, select);
  3006. datasetDropDown = document.getElementById('datasets');
  3007. datasetButtonLast = document.getElementById('lastDataset');
  3008. datasetButtonPrev = document.getElementById('prevDataset');
  3009. datasetButtonNext = document.getElementById('nextDataset');
  3010. }
  3011. position = addOptionElement
  3012. (
  3013. position + 5,
  3014. '&nbsp;<input type="button" id="maxAbsoluteDepthDecrease" value="-"/>\
  3015. <span id="maxAbsoluteDepth"></span>\
  3016. &nbsp;<input type="button" id="maxAbsoluteDepthIncrease" value="+"/> Max depth',
  3017. 'Maximum depth to display, counted from the top level \
  3018. and including collapsed wedges.'
  3019. );
  3020. position = addOptionElement
  3021. (
  3022. position,
  3023. '&nbsp;<input type="button" id="fontSizeDecrease" value="-"/>\
  3024. <span id="fontSize"></span>\
  3025. &nbsp;<input type="button" id="fontSizeIncrease" value="+"/> Font size'
  3026. );
  3027. if ( hueName )
  3028. {
  3029. hueDisplayName = attributes[attributeIndex(hueName)].displayName;
  3030. position = addOptionElement
  3031. (
  3032. position + 5,
  3033. '<div style="float:left">&nbsp;</div>' +
  3034. '<input type="checkbox" id="useHue" style="float:left" ' +
  3035. '/><div style="float:left">Color by<br/>' + hueDisplayName +
  3036. '</div>'
  3037. );
  3038. useHueCheckBox = document.getElementById('useHue');
  3039. useHueCheckBox.checked = hueDefault;
  3040. useHueCheckBox.onclick = handleResize;
  3041. }
  3042. /*
  3043. position = addOptionElement
  3044. (
  3045. position + 5,
  3046. '&nbsp;<input type="checkbox" id="shorten" checked="checked" />Shorten labels</div>',
  3047. 'Prevent labels from overlapping by shortening them'
  3048. );
  3049. position = addOptionElement
  3050. (
  3051. position,
  3052. '&nbsp;<input type="checkbox" id="compress" checked="checked" />Compress',
  3053. 'Compress wedges if needed to show the entire depth'
  3054. );
  3055. */
  3056. position = addOptionElement
  3057. (
  3058. position,
  3059. '&nbsp;<input type="checkbox" id="collapse" checked="checked" />Collapse',
  3060. 'Collapse wedges that are redundant (entirely composed of another wedge)'
  3061. );
  3062. position = addOptionElement
  3063. (
  3064. position + 5,
  3065. '&nbsp;<input type="button" id="snapshot" value="Snapshot"/>',
  3066. 'Render the current view as SVG (Scalable Vector Graphics), a publication-\
  3067. quality format that can be printed and saved (see Help for browser compatibility)'
  3068. );
  3069. position = addOptionElement
  3070. (
  3071. position + 5,
  3072. '&nbsp;<input type="button" id="linkButton" value="Link"/>\
  3073. <input type="text" size="30" id="linkText"/>',
  3074. 'Show a link to this view that can be copied for bookmarking or sharing'
  3075. );
  3076. position = addOptionElement
  3077. (
  3078. position + 5,
  3079. '&nbsp;<input type="button" id="help" value="?"\
  3080. onclick="window.open(\'https://sourceforge.net/p/krona/wiki/Browsing%20Krona%20charts/\', \'help\')"/>',
  3081. 'Help'
  3082. );
  3083. }
  3084. function arrow(angleStart, angleEnd, radiusInner)
  3085. {
  3086. if ( context.globalAlpha == 0 )
  3087. {
  3088. return;
  3089. }
  3090. var angleCenter = (angleStart + angleEnd) / 2;
  3091. var radiusArrowInner = radiusInner - gRadius / 10;//nodeRadius * gRadius;
  3092. var radiusArrowOuter = gRadius * 1.1;//(1 + nodeRadius);
  3093. var radiusArrowCenter = (radiusArrowInner + radiusArrowOuter) / 2;
  3094. var pointLength = (radiusArrowOuter - radiusArrowInner) / 5;
  3095. context.fillStyle = highlightFill;
  3096. context.lineWidth = highlightLineWidth;
  3097. // First, mask out the first half of the arrow. This will prevent the tips
  3098. // from superimposing if the arrow goes most of the way around the circle.
  3099. // Masking is done by setting the clipping region to the inverse of the
  3100. // half-arrow, which is defined by cutting the half-arrow out of a large
  3101. // rectangle
  3102. //
  3103. context.beginPath();
  3104. context.arc(0, 0, radiusInner, angleCenter, angleEnd, false);
  3105. context.lineTo
  3106. (
  3107. radiusArrowInner * Math.cos(angleEnd),
  3108. radiusArrowInner * Math.sin(angleEnd)
  3109. );
  3110. context.lineTo
  3111. (
  3112. radiusArrowCenter * Math.cos(angleEnd) - pointLength * Math.sin(angleEnd),
  3113. radiusArrowCenter * Math.sin(angleEnd) + pointLength * Math.cos(angleEnd)
  3114. );
  3115. context.lineTo
  3116. (
  3117. radiusArrowOuter * Math.cos(angleEnd),
  3118. radiusArrowOuter * Math.sin(angleEnd)
  3119. );
  3120. context.arc(0, 0, gRadius, angleEnd, angleCenter, true);
  3121. context.closePath();
  3122. context.moveTo(-imageWidth, -imageHeight);
  3123. context.lineTo(imageWidth, -imageHeight);
  3124. context.lineTo(imageWidth, imageHeight);
  3125. context.lineTo(-imageWidth, imageHeight);
  3126. context.closePath();
  3127. context.save();
  3128. context.clip();
  3129. // Next, draw the other half-arrow with the first half masked out
  3130. //
  3131. context.beginPath();
  3132. context.arc(0, 0, radiusInner, angleCenter, angleStart, true);
  3133. context.lineTo
  3134. (
  3135. radiusArrowInner * Math.cos(angleStart),
  3136. radiusArrowInner * Math.sin(angleStart)
  3137. );
  3138. context.lineTo
  3139. (
  3140. radiusArrowCenter * Math.cos(angleStart) + pointLength * Math.sin(angleStart),
  3141. radiusArrowCenter * Math.sin(angleStart) - pointLength * Math.cos(angleStart)
  3142. );
  3143. context.lineTo
  3144. (
  3145. radiusArrowOuter * Math.cos(angleStart),
  3146. radiusArrowOuter * Math.sin(angleStart)
  3147. );
  3148. context.arc(0, 0, gRadius, angleStart, angleCenter, false);
  3149. context.fill();
  3150. context.stroke();
  3151. // Finally, remove the clipping region and draw the first half-arrow. This
  3152. // half is extended slightly to fill the seam.
  3153. //
  3154. context.restore();
  3155. context.beginPath();
  3156. context.arc(0, 0, radiusInner, angleCenter - 2 / (2 * Math.PI * radiusInner), angleEnd, false);
  3157. context.lineTo
  3158. (
  3159. radiusArrowInner * Math.cos(angleEnd),
  3160. radiusArrowInner * Math.sin(angleEnd)
  3161. );
  3162. context.lineTo
  3163. (
  3164. radiusArrowCenter * Math.cos(angleEnd) - pointLength * Math.sin(angleEnd),
  3165. radiusArrowCenter * Math.sin(angleEnd) + pointLength * Math.cos(angleEnd)
  3166. );
  3167. context.lineTo
  3168. (
  3169. radiusArrowOuter * Math.cos(angleEnd),
  3170. radiusArrowOuter * Math.sin(angleEnd)
  3171. );
  3172. context.arc(0, 0, gRadius, angleEnd, angleCenter - 2 / (2 * Math.PI * gRadius), true);
  3173. context.fill();
  3174. context.stroke();
  3175. }
  3176. function attributeIndex(aname)
  3177. {
  3178. for ( var i = 0 ; i < attributes.length; i++ )
  3179. {
  3180. if ( aname == attributes[i].name )
  3181. {
  3182. return i;
  3183. }
  3184. }
  3185. return null;
  3186. }
  3187. function checkHighlight()
  3188. {
  3189. var lastHighlightedNode = highlightedNode;
  3190. var lastHighlightingHidden = highlightingHidden;
  3191. highlightedNode = selectedNode;
  3192. resetKeyOffset();
  3193. if ( progress == 1 )
  3194. {
  3195. selectedNode.checkHighlight();
  3196. if ( selectedNode.getParent() )
  3197. {
  3198. selectedNode.getParent().checkHighlightCenter();
  3199. }
  3200. focusNode.checkHighlightMap();
  3201. }
  3202. if ( highlightedNode != selectedNode )
  3203. {
  3204. if ( highlightedNode == focusNode )
  3205. {
  3206. // canvas.style.display='none';
  3207. // window.resizeBy(1,0);
  3208. // canvas.style.cursor='ew-resize';
  3209. // window.resizeBy(-1,0);
  3210. // canvas.style.display='inline';
  3211. }
  3212. else
  3213. {
  3214. // canvas.style.cursor='pointer';
  3215. }
  3216. }
  3217. else
  3218. {
  3219. // canvas.style.cursor='auto';
  3220. }
  3221. if
  3222. (
  3223. (
  3224. true ||
  3225. highlightedNode != lastHighlightedNode ||
  3226. highlightingHidden != highlightingHiddenLast
  3227. ) &&
  3228. progress == 1
  3229. )
  3230. {
  3231. draw(); // TODO: handle in update()
  3232. }
  3233. }
  3234. function checkSelectedCollapse()
  3235. {
  3236. var newNode = selectedNode;
  3237. while ( newNode.getCollapse() )
  3238. {
  3239. newNode = newNode.children[0];
  3240. }
  3241. if ( newNode.children.length == 0 )
  3242. {
  3243. newNode = newNode.getParent();
  3244. }
  3245. if ( newNode != selectedNode )
  3246. {
  3247. selectNode(newNode);
  3248. }
  3249. }
  3250. function clearSearch()
  3251. {
  3252. search.value = '';
  3253. onSearchChange();
  3254. }
  3255. function createSVG()
  3256. {
  3257. svgNS = "http://www.w3.org/2000/svg";
  3258. var SVG = {};
  3259. SVG.xlinkns = "http://www.w3.org/1999/xlink";
  3260. var newSVG = document.createElementNS(svgNS, "svg:svg");
  3261. newSVG.setAttribute("id", "canvas");
  3262. // How big is the canvas in pixels
  3263. newSVG.setAttribute("width", '100%');
  3264. newSVG.setAttribute("height", '100%');
  3265. // Set the coordinates used by drawings in the canvas
  3266. // newSVG.setAttribute("viewBox", "0 0 " + imageWidth + " " + imageHeight);
  3267. // Define the XLink namespace that SVG uses
  3268. newSVG.setAttributeNS
  3269. (
  3270. "http://www.w3.org/2000/xmlns/",
  3271. "xmlns:xlink",
  3272. SVG.xlinkns
  3273. );
  3274. return newSVG;
  3275. }
  3276. function degrees(radians)
  3277. {
  3278. return radians * 180 / Math.PI;
  3279. }
  3280. function draw()
  3281. {
  3282. tweenFrames++;
  3283. //resize();
  3284. // context.fillRect(0, 0, imageWidth, imageHeight);
  3285. context.clearRect(0, 0, imageWidth, imageHeight);
  3286. context.font = fontNormal;
  3287. context.textBaseline = 'middle';
  3288. //context.strokeStyle = 'rgba(0, 0, 0, 0.3)';
  3289. context.translate(centerX, centerY);
  3290. resetKeyOffset();
  3291. head.draw(false, false); // draw pie slices
  3292. head.draw(true, false); // draw labels
  3293. var pathRoot = selectedNode;
  3294. if ( focusNode != 0 && focusNode != selectedNode )
  3295. {
  3296. context.globalAlpha = 1;
  3297. focusNode.drawHighlight(true);
  3298. pathRoot = focusNode;
  3299. }
  3300. if
  3301. (
  3302. highlightedNode &&
  3303. highlightedNode.getDepth() >= selectedNode.getDepth() &&
  3304. highlightedNode != focusNode
  3305. )
  3306. {
  3307. if
  3308. (
  3309. progress == 1 &&
  3310. highlightedNode != selectedNode &&
  3311. (
  3312. highlightedNode != focusNode ||
  3313. focusNode.children.length > 0
  3314. )
  3315. )
  3316. {
  3317. context.globalAlpha = 1;
  3318. highlightedNode.drawHighlight(true);
  3319. }
  3320. //pathRoot = highlightedNode;
  3321. }
  3322. else if
  3323. (
  3324. progress == 1 &&
  3325. highlightedNode.getDepth() < selectedNode.getDepth()
  3326. )
  3327. {
  3328. context.globalAlpha = 1;
  3329. highlightedNode.drawHighlightCenter();
  3330. }
  3331. if ( quickLook && false) // TEMP
  3332. {
  3333. context.globalAlpha = 1 - progress / 2;
  3334. selectedNode.drawHighlight(true);
  3335. }
  3336. else if ( progress < 1 )//&& zoomOut() )
  3337. {
  3338. if ( !zoomOut)//() )
  3339. {
  3340. context.globalAlpha = selectedNode.alphaLine.current();
  3341. selectedNode.drawHighlight(true);
  3342. }
  3343. else if ( selectedNodeLast )
  3344. {
  3345. context.globalAlpha = 1 - 4 * Math.pow(progress - .5, 2);
  3346. selectedNodeLast.drawHighlight(false);
  3347. }
  3348. }
  3349. drawDatasetName();
  3350. //drawHistory();
  3351. context.translate(-centerX, -centerY);
  3352. context.globalAlpha = 1;
  3353. mapRadius =
  3354. (imageHeight / 2 - details.clientHeight - details.offsetTop) /
  3355. (pathRoot.getDepth() - 1) * 3 / 4 / 2;
  3356. if ( mapRadius > maxMapRadius )
  3357. {
  3358. mapRadius = maxMapRadius;
  3359. }
  3360. mapBuffer = mapRadius / 2;
  3361. //context.font = fontNormal;
  3362. pathRoot.drawMap(pathRoot);
  3363. if ( hueDisplayName && useHue() )
  3364. {
  3365. drawLegend();
  3366. }
  3367. }
  3368. function drawBubble(angle, radius, width, radial, flip)
  3369. {
  3370. var height = fontSize * 2;
  3371. var x;
  3372. var y;
  3373. width = width + fontSize;
  3374. if ( radial )
  3375. {
  3376. y = -fontSize;
  3377. if ( flip )
  3378. {
  3379. x = radius - width + fontSize / 2;
  3380. }
  3381. else
  3382. {
  3383. x = radius - fontSize / 2;
  3384. }
  3385. }
  3386. else
  3387. {
  3388. x = -width / 2;
  3389. y = -radius - fontSize;
  3390. }
  3391. if ( snapshotMode )
  3392. {
  3393. drawBubbleSVG(x + centerX, y + centerY, width, height, fontSize, angle);
  3394. }
  3395. else
  3396. {
  3397. drawBubbleCanvas(x, y, width, height, fontSize, angle);
  3398. }
  3399. }
  3400. function drawBubbleCanvas(x, y, width, height, radius, rotation)
  3401. {
  3402. context.strokeStyle = 'black';
  3403. context.lineWidth = highlightLineWidth;
  3404. context.fillStyle = 'rgba(255, 255, 255, .75)';
  3405. context.rotate(rotation);
  3406. roundedRectangle(x, y, width, fontSize * 2, fontSize);
  3407. context.fill();
  3408. context.stroke();
  3409. context.rotate(-rotation);
  3410. }
  3411. function drawBubbleSVG(x, y, width, height, radius, rotation)
  3412. {
  3413. svg +=
  3414. '<rect x="' + x + '" y="' + y +
  3415. '" width="' + width +
  3416. '" height="' + height +
  3417. '" rx="' + radius +
  3418. '" ry="' + radius +
  3419. '" fill="rgba(255, 255, 255, .75)' +
  3420. '" class="highlight" ' +
  3421. 'transform="rotate(' +
  3422. degrees(rotation) + ',' + centerX + ',' + centerY +
  3423. ')"/>';
  3424. }
  3425. function drawDatasetName()
  3426. {
  3427. var alpha = datasetAlpha.current();
  3428. if ( alpha > 0 )
  3429. {
  3430. var radius = gRadius * compressedRadii[0] / -2;
  3431. if ( alpha > 1 )
  3432. {
  3433. alpha = 1;
  3434. }
  3435. context.globalAlpha = alpha;
  3436. drawBubble(0, -radius, datasetWidths[currentDataset], false, false);
  3437. drawText(datasetNames[currentDataset], 0, radius, 0, 'center', true);
  3438. }
  3439. }
  3440. function drawHistory()
  3441. {
  3442. var alpha = 1;
  3443. context.textAlign = 'center';
  3444. for ( var i = 0; i < nodeHistoryPosition && alpha > 0; i++ )
  3445. {
  3446. context.globalAlpha = alpha - historyAlphaDelta * tweenFactor;
  3447. context.fillText
  3448. (
  3449. nodeHistory[nodeHistoryPosition - i - 1].name,
  3450. 0,
  3451. (i + tweenFactor) * historySpacingFactor * fontSize - 1
  3452. );
  3453. if ( alpha > 0 )
  3454. {
  3455. alpha -= historyAlphaDelta;
  3456. }
  3457. }
  3458. context.globalAlpha = 1;
  3459. }
  3460. function drawLegend()
  3461. {
  3462. var left = imageWidth * .01;
  3463. var width = imageHeight * .0265;
  3464. var height = imageHeight * .15;
  3465. var top = imageHeight - fontSize * 3.5 - height;
  3466. var textLeft = left + width + fontSize / 2;
  3467. context.fillStyle = 'black';
  3468. context.textAlign = 'start';
  3469. context.font = fontNormal;
  3470. // context.fillText(valueStartText, textLeft, top + height);
  3471. // context.fillText(valueEndText, textLeft, top);
  3472. context.fillText(hueDisplayName, left, imageHeight - fontSize * 1.5);
  3473. var gradient = context.createLinearGradient(0, top + height, 0, top);
  3474. for ( var i = 0; i < hueStopPositions.length; i++ )
  3475. {
  3476. gradient.addColorStop(hueStopPositions[i], hueStopHsl[i]);
  3477. var textY = top + (1 - hueStopPositions[i]) * height;
  3478. if
  3479. (
  3480. i == 0 ||
  3481. i == hueStopPositions.length - 1 ||
  3482. textY > top + fontSize && textY < top + height - fontSize
  3483. )
  3484. {
  3485. context.fillText(hueStopText[i], textLeft, textY);
  3486. }
  3487. }
  3488. context.fillStyle = gradient;
  3489. context.fillRect(left, top, width, height);
  3490. context.lineWidth = thinLineWidth;
  3491. context.strokeRect(left, top, width, height);
  3492. }
  3493. function drawLegendSVG()
  3494. {
  3495. var left = imageWidth * .01;
  3496. var width = imageHeight * .0265;
  3497. var height = imageHeight * .15;
  3498. var top = imageHeight - fontSize * 3.5 - height;
  3499. var textLeft = left + width + fontSize / 2;
  3500. var text = '';
  3501. text += svgText(hueDisplayName, left, imageHeight - fontSize * 1.5);
  3502. var svgtest = '<linearGradient id="gradient" x1="0%" y1="100%" x2="0%" y2="0%">';
  3503. for ( var i = 0; i < hueStopPositions.length; i++ )
  3504. {
  3505. svgtest +=
  3506. '<stop offset="' + round(hueStopPositions[i] * 100) +
  3507. '%" style="stop-color:' + hueStopHsl[i] + '"/>';
  3508. var textY = top + (1 - hueStopPositions[i]) * height;
  3509. if
  3510. (
  3511. i == 0 ||
  3512. i == hueStopPositions.length - 1 ||
  3513. textY > top + fontSize && textY < top + height - fontSize
  3514. )
  3515. {
  3516. text += svgText(hueStopText[i], textLeft, textY);
  3517. }
  3518. }
  3519. svgtest += '</linearGradient>';
  3520. //alert(svgtest);
  3521. svg += svgtest;
  3522. svg +=
  3523. '<rect style="fill:url(#gradient)" x="' + left + '" y="' + top +
  3524. '" width="' + width + '" height="' + height + '"/>';
  3525. svg += text;
  3526. }
  3527. function drawSearchHighlights(label, bubbleX, bubbleY, rotation, center)
  3528. {
  3529. var index = -1;
  3530. var labelLength = label.length;
  3531. bubbleX -= fontSize / 4;
  3532. do
  3533. {
  3534. index = label.toLowerCase().indexOf(search.value.toLowerCase(), index + 1);
  3535. if ( index != -1 && index < labelLength )
  3536. {
  3537. var dim = context.measureText(label.substr(0, index));
  3538. var x = bubbleX + dim.width;
  3539. dim = context.measureText(label.substr(index, search.value.length));
  3540. var y = bubbleY - fontSize * 3 / 4;
  3541. var width = dim.width + fontSize / 2;
  3542. var height = fontSize * 3 / 2;
  3543. var radius = fontSize / 2;
  3544. if ( snapshotMode )
  3545. {
  3546. if ( center )
  3547. {
  3548. x += centerX;
  3549. y += centerY;
  3550. }
  3551. svg +=
  3552. '<rect x="' + x + '" y="' + y +
  3553. '" width="' + width +
  3554. '" height="' + height +
  3555. '" rx="' + radius +
  3556. '" ry="' + radius +
  3557. '" class="searchHighlight' +
  3558. '" transform="rotate(' +
  3559. degrees(rotation) + ',' + centerX + ',' + centerY +
  3560. ')"/>';
  3561. }
  3562. else
  3563. {
  3564. context.fillStyle = 'rgb(255, 255, 100)';
  3565. context.rotate(rotation);
  3566. roundedRectangle(x, y, width, height, radius);
  3567. context.fill();
  3568. context.rotate(-rotation);
  3569. }
  3570. }
  3571. }
  3572. while ( index != -1 && index < labelLength );
  3573. }
  3574. function drawText(text, x, y, angle, anchor, bold)
  3575. {
  3576. if ( snapshotMode )
  3577. {
  3578. svg +=
  3579. '<text x="' + (centerX + x) + '" y="' + (centerY + y) +
  3580. '" text-anchor="' + anchor + '" style="font-weight:' + (bold ? 'bold' : 'normal') +
  3581. '" transform="rotate(' + degrees(angle) + ',' + centerX + ',' + centerY + ')">' +
  3582. text + '</text>';
  3583. }
  3584. else
  3585. {
  3586. context.fillStyle = 'black';
  3587. context.textAlign = anchor;
  3588. context.font = bold ? fontBold : fontNormal;
  3589. context.rotate(angle);
  3590. context.fillText(text, x, y);
  3591. context.rotate(-angle);
  3592. }
  3593. }
  3594. function drawTextPolar
  3595. (
  3596. text,
  3597. innerText,
  3598. angle,
  3599. radius,
  3600. radial,
  3601. bubble,
  3602. bold,
  3603. searchResult,
  3604. searchResults
  3605. )
  3606. {
  3607. var anchor;
  3608. var textX;
  3609. var textY;
  3610. var spacer;
  3611. var totalText = text;
  3612. var flip;
  3613. if ( snapshotMode )
  3614. {
  3615. spacer = '&#160;&#160;&#160;';
  3616. }
  3617. else
  3618. {
  3619. spacer = ' ';
  3620. }
  3621. if ( radial )
  3622. {
  3623. flip = angle < 3 * Math.PI / 2;
  3624. if ( flip )
  3625. {
  3626. angle -= Math.PI;
  3627. radius = -radius;
  3628. anchor = 'end';
  3629. if ( innerText )
  3630. {
  3631. totalText = text + spacer + innerText;
  3632. }
  3633. }
  3634. else
  3635. {
  3636. anchor = 'start';
  3637. if ( innerText )
  3638. {
  3639. totalText = innerText + spacer + text;
  3640. }
  3641. }
  3642. textX = radius;
  3643. textY = 0;
  3644. }
  3645. else
  3646. {
  3647. flip = angle < Math.PI || angle > 2 * Math.PI;
  3648. var label;
  3649. anchor = snapshotMode ? 'middle' : 'center';
  3650. if ( flip )
  3651. {
  3652. angle -= Math.PI;
  3653. radius = -radius;
  3654. }
  3655. angle += Math.PI / 2;
  3656. textX = 0;
  3657. textY = -radius;
  3658. }
  3659. if ( bubble )
  3660. {
  3661. var textActual = totalText;
  3662. if ( innerText && snapshotMode )
  3663. {
  3664. if ( flip )
  3665. {
  3666. textActual = text + ' ' + innerText;
  3667. }
  3668. else
  3669. {
  3670. textActual = innerText + ' ' + text;
  3671. }
  3672. }
  3673. if ( searchResults )
  3674. {
  3675. textActual = textActual + searchResultString(searchResults);
  3676. }
  3677. var textWidth = measureText(textActual, bold);
  3678. var x = textX;
  3679. if ( anchor == 'end' )
  3680. {
  3681. x -= textWidth;
  3682. }
  3683. else if ( anchor != 'start' )
  3684. {
  3685. // centered
  3686. x -= textWidth / 2;
  3687. }
  3688. drawBubble(angle, radius, textWidth, radial, flip);
  3689. if ( searchResult )
  3690. {
  3691. drawSearchHighlights
  3692. (
  3693. textActual,
  3694. x,
  3695. textY,
  3696. angle,
  3697. true
  3698. )
  3699. }
  3700. }
  3701. if ( searchResults )
  3702. {
  3703. totalText = totalText + searchResultString(searchResults);
  3704. }
  3705. drawText(totalText, textX, textY, angle, anchor, bold);
  3706. return flip;
  3707. }
  3708. function drawTick(start, length, angle)
  3709. {
  3710. if ( snapshotMode )
  3711. {
  3712. svg +=
  3713. '<line x1="' + (centerX + start) +
  3714. '" y1="' + centerY +
  3715. '" x2="' + (centerX + start + length) +
  3716. '" y2="' + centerY +
  3717. '" class="tick" transform="rotate(' +
  3718. degrees(angle) + ',' + centerX + ',' + centerY +
  3719. ')"/>';
  3720. }
  3721. else
  3722. {
  3723. context.rotate(angle);
  3724. context.beginPath();
  3725. context.moveTo(start, 0);
  3726. context.lineTo(start + length, 0);
  3727. context.lineWidth = thinLineWidth * 2;
  3728. context.stroke();
  3729. context.rotate(-angle);
  3730. }
  3731. }
  3732. function drawWedge
  3733. (
  3734. angleStart,
  3735. angleEnd,
  3736. radiusInner,
  3737. radiusOuter,
  3738. color,
  3739. patternAlpha,
  3740. highlight
  3741. )
  3742. {
  3743. if ( context.globalAlpha == 0 )
  3744. {
  3745. return;
  3746. }
  3747. if ( snapshotMode )
  3748. {
  3749. if ( angleEnd == angleStart + Math.PI * 2 )
  3750. {
  3751. // fudge to prevent overlap, which causes arc ambiguity
  3752. //
  3753. angleEnd -= .1 / gRadius;
  3754. }
  3755. var longArc = angleEnd - angleStart > Math.PI ? 1 : 0;
  3756. var x1 = centerX + radiusInner * Math.cos(angleStart);
  3757. var y1 = centerY + radiusInner * Math.sin(angleStart);
  3758. var x2 = centerX + gRadius * Math.cos(angleStart);
  3759. var y2 = centerY + gRadius * Math.sin(angleStart);
  3760. var x3 = centerX + gRadius * Math.cos(angleEnd);
  3761. var y3 = centerY + gRadius * Math.sin(angleEnd);
  3762. var x4 = centerX + radiusInner * Math.cos(angleEnd);
  3763. var y4 = centerY + radiusInner * Math.sin(angleEnd);
  3764. var dArray =
  3765. [
  3766. " M ", x1, ",", y1,
  3767. " L ", x2, ",", y2,
  3768. " A ", gRadius, ",", gRadius, " 0 ", longArc, ",1 ", x3, ",", y3,
  3769. " L ", x4, ",", y4,
  3770. " A ", radiusInner, ",", radiusInner, " 0 ", longArc, " 0 ", x1, ",", y1,
  3771. " Z "
  3772. ];
  3773. svg +=
  3774. '<path class="'+ (highlight ? 'highlight' : 'wedge') + '" fill="' + color +
  3775. '" d="' + dArray.join('') + '"/>';
  3776. if ( patternAlpha > 0 )
  3777. {
  3778. svg +=
  3779. '<path class="wedge" fill="url(#hiddenPattern)" d="' +
  3780. dArray.join('') + '"/>';
  3781. }
  3782. }
  3783. else
  3784. {
  3785. // fudge to prevent seams during animation
  3786. //
  3787. angleEnd += 1 / gRadius;
  3788. context.fillStyle = color;
  3789. context.beginPath();
  3790. context.arc(0, 0, radiusInner, angleStart, angleEnd, false);
  3791. context.arc(0, 0, radiusOuter, angleEnd, angleStart, true);
  3792. context.closePath();
  3793. context.fill();
  3794. if ( patternAlpha > 0 )
  3795. {
  3796. context.save();
  3797. context.clip();
  3798. context.globalAlpha = patternAlpha;
  3799. context.fillStyle = hiddenPattern;
  3800. context.fill();
  3801. context.restore();
  3802. }
  3803. if ( highlight )
  3804. {
  3805. context.lineWidth = highlight ? highlightLineWidth : thinLineWidth;
  3806. context.strokeStyle = 'black';
  3807. context.stroke();
  3808. }
  3809. }
  3810. }
  3811. function expand(node)
  3812. {
  3813. selectNode(node);
  3814. updateView();
  3815. }
  3816. function focusLost()
  3817. {
  3818. mouseX = -1;
  3819. mouseY = -1;
  3820. checkHighlight();
  3821. document.body.style.cursor = 'auto';
  3822. }
  3823. function fontSizeDecrease()
  3824. {
  3825. if ( fontSize > 1 )
  3826. {
  3827. fontSize--;
  3828. updateViewNeeded = true;
  3829. }
  3830. }
  3831. function fontSizeIncrease()
  3832. {
  3833. fontSize++;
  3834. updateViewNeeded = true;
  3835. }
  3836. function getGetString(name, value, bool)
  3837. {
  3838. return name + '=' + (bool ? value ? 'true' : 'false' : value);
  3839. }
  3840. function hideLink()
  3841. {
  3842. hide(linkText);
  3843. show(linkButton);
  3844. }
  3845. function show(object)
  3846. {
  3847. object.style.display = 'inline';
  3848. }
  3849. function hide(object)
  3850. {
  3851. object.style.display = 'none';
  3852. }
  3853. function showLink()
  3854. {
  3855. var urlHalves = String(document.location).split('?');
  3856. var newGetVariables = new Array();
  3857. newGetVariables.push
  3858. (
  3859. getGetString('dataset', currentDataset, false),
  3860. getGetString('node', selectedNode.id, false),
  3861. getGetString('collapse', collapse, true),
  3862. getGetString('color', useHue(), true),
  3863. getGetString('depth', maxAbsoluteDepth - 1, false),
  3864. getGetString('font', fontSize, false),
  3865. getGetString('key', showKeys, true)
  3866. );
  3867. hide(linkButton);
  3868. show(linkText);
  3869. linkText.value = urlHalves[0] + '?' + getVariables.concat(newGetVariables).join('&');
  3870. //linkText.disabled = false;
  3871. linkText.focus();
  3872. linkText.select();
  3873. //linkText.disabled = true;
  3874. // document.location = urlHalves[0] + '?' + getVariables.join('&');
  3875. }
  3876. function getFirstChild(element)
  3877. {
  3878. element = element.firstChild;
  3879. if ( element && element.nodeType != 1 )
  3880. {
  3881. element = getNextSibling(element);
  3882. }
  3883. return element;
  3884. }
  3885. function getNextSibling(element)
  3886. {
  3887. do
  3888. {
  3889. element = element.nextSibling;
  3890. }
  3891. while ( element && element.nodeType != 1 );
  3892. return element;
  3893. }
  3894. function getPercentage(fraction)
  3895. {
  3896. return round(fraction * 100);
  3897. }
  3898. function hslText(hue)
  3899. {
  3900. if ( 1 || snapshotMode )
  3901. {
  3902. // Safari doesn't seem to allow hsl() in SVG
  3903. var rgb = hslToRgb(hue, saturation, (lightnessBase + lightnessMax) / 2);
  3904. return rgbText(rgb.r, rgb.g, rgb.b);
  3905. }
  3906. else
  3907. {
  3908. var hslArray =
  3909. [
  3910. 'hsl(',
  3911. Math.floor(hue * 360),
  3912. ',',
  3913. Math.floor(saturation * 100),
  3914. '%,',
  3915. Math.floor((lightnessBase + lightnessMax) * 50),
  3916. '%)'
  3917. ];
  3918. return hslArray.join('');
  3919. }
  3920. }
  3921. function hslToRgb(h, s, l)
  3922. {
  3923. var m1, m2;
  3924. var r, g, b;
  3925. if (s == 0)
  3926. {
  3927. r = g = b = Math.floor((l * 255));
  3928. }
  3929. else
  3930. {
  3931. if (l <= 0.5)
  3932. {
  3933. m2 = l * (s + 1);
  3934. }
  3935. else
  3936. {
  3937. m2 = l + s - l * s;
  3938. }
  3939. m1 = l * 2 - m2;
  3940. r = Math.floor(hueToRgb(m1, m2, h + 1 / 3));
  3941. g = Math.floor(hueToRgb(m1, m2, h));
  3942. b = Math.floor(hueToRgb(m1, m2, h - 1/3));
  3943. }
  3944. return {r: r, g: g, b: b};
  3945. }
  3946. function hueToRgb(m1, m2, hue)
  3947. {
  3948. var v;
  3949. while (hue < 0)
  3950. {
  3951. hue += 1;
  3952. }
  3953. while (hue > 1)
  3954. {
  3955. hue -= 1;
  3956. }
  3957. if (6 * hue < 1)
  3958. v = m1 + (m2 - m1) * hue * 6;
  3959. else if (2 * hue < 1)
  3960. v = m2;
  3961. else if (3 * hue < 2)
  3962. v = m1 + (m2 - m1) * (2/3 - hue) * 6;
  3963. else
  3964. v = m1;
  3965. return 255 * v;
  3966. }
  3967. function interpolateHue(hueStart, hueEnd, valueStart, valueEnd)
  3968. {
  3969. // since the gradient will be RGB based, we need to add stops to hit all the
  3970. // colors in the hue spectrum
  3971. hueStopPositions = new Array();
  3972. hueStopHsl = new Array();
  3973. hueStopText = new Array();
  3974. hueStopPositions.push(0);
  3975. hueStopHsl.push(hslText(hueStart));
  3976. hueStopText.push(round(valueStart));
  3977. for
  3978. (
  3979. var i = (hueStart > hueEnd ? 5 / 6 : 1 / 6);
  3980. (hueStart > hueEnd ? i > 0 : i < 1);
  3981. i += (hueStart > hueEnd ? -1 : 1) / 6
  3982. )
  3983. {
  3984. if
  3985. (
  3986. hueStart > hueEnd ?
  3987. i > hueEnd && i < hueStart :
  3988. i > hueStart && i < hueEnd
  3989. )
  3990. {
  3991. hueStopPositions.push(lerp(i, hueStart, hueEnd, 0, 1));
  3992. hueStopHsl.push(hslText(i));
  3993. hueStopText.push(round(lerp
  3994. (
  3995. i,
  3996. hueStart,
  3997. hueEnd,
  3998. valueStart,
  3999. valueEnd
  4000. )));
  4001. }
  4002. }
  4003. hueStopPositions.push(1);
  4004. hueStopHsl.push(hslText(hueEnd));
  4005. hueStopText.push(round(valueEnd));
  4006. }
  4007. function keyLineAngle(angle, keyAngle, bendRadius, keyX, keyY, pointsX, pointsY)
  4008. {
  4009. if ( angle < Math.PI / 2 && keyY < bendRadius * Math.sin(angle)
  4010. || angle > Math.PI / 2 && keyY < bendRadius)
  4011. {
  4012. return Math.asin(keyY / bendRadius);
  4013. }
  4014. else
  4015. {
  4016. // find the angle of the normal to a tangent line that goes to
  4017. // the label
  4018. var textDist = Math.sqrt
  4019. (
  4020. Math.pow(keyX, 2) +
  4021. Math.pow(keyY, 2)
  4022. );
  4023. var tanAngle = Math.acos(bendRadius / textDist) + keyAngle;
  4024. if ( angle < tanAngle || angle < Math.PI / 2 )//|| labelLeft < centerX )
  4025. {
  4026. // angle doesn't reach far enough for tangent; collapse and
  4027. // connect directly to label
  4028. if ( keyY / Math.tan(angle) > 0 )
  4029. {
  4030. pointsX.push(keyY / Math.tan(angle));
  4031. pointsY.push(keyY);
  4032. }
  4033. else
  4034. {
  4035. pointsX.push(bendRadius * Math.cos(angle));
  4036. pointsY.push(bendRadius * Math.sin(angle));
  4037. }
  4038. return angle;
  4039. }
  4040. else
  4041. {
  4042. return tanAngle;
  4043. }
  4044. }
  4045. }
  4046. function keyOffset()
  4047. {
  4048. return imageHeight - (keys - currentKey + 1) * (keySize + keyBuffer) + keyBuffer - margin;
  4049. }
  4050. function lerp(value, fromStart, fromEnd, toStart, toEnd)
  4051. {
  4052. return (value - fromStart) *
  4053. (toEnd - toStart) /
  4054. (fromEnd - fromStart) +
  4055. toStart;
  4056. }
  4057. function createCanvas()
  4058. {
  4059. canvas = document.createElement('canvas');
  4060. document.body.appendChild(canvas);
  4061. context = canvas.getContext('2d');
  4062. }
  4063. function load()
  4064. {
  4065. document.body.style.overflow = "hidden";
  4066. document.body.style.margin = 0;
  4067. createCanvas();
  4068. if ( context == undefined )
  4069. {
  4070. document.body.innerHTML = '\
  4071. <br/>This browser does not support HTML5 (see \
  4072. <a href="http://sourceforge.net/p/krona/wiki/Browser%20support/">Browser support</a>).\
  4073. ';
  4074. return;
  4075. }
  4076. if ( typeof context.fillText != 'function' )
  4077. {
  4078. document.body.innerHTML = '\
  4079. <br/>This browser does not support HTML5 canvas text (see \
  4080. <a href="http://sourceforge.net/p/krona/wiki/Browser%20support/">Browser support</a>).\
  4081. ';
  4082. return;
  4083. }
  4084. resize();
  4085. var kronaElement = document.getElementsByTagName('krona')[0];
  4086. var magnitudeName;
  4087. var hueName;
  4088. var hueDefault;
  4089. var hueStart;
  4090. var hueEnd;
  4091. var valueStart;
  4092. var valueEnd;
  4093. if ( kronaElement.getAttribute('collapse') != undefined )
  4094. {
  4095. collapse = kronaElement.getAttribute('collapse') == 'true';
  4096. }
  4097. if ( kronaElement.getAttribute('key') != undefined )
  4098. {
  4099. showKeys = kronaElement.getAttribute('key') == 'true';
  4100. }
  4101. for
  4102. (
  4103. var element = getFirstChild(kronaElement);
  4104. element;
  4105. element = getNextSibling(element)
  4106. )
  4107. {
  4108. switch ( element.tagName.toLowerCase() )
  4109. {
  4110. case 'attributes':
  4111. magnitudeName = element.getAttribute('magnitude');
  4112. //
  4113. for
  4114. (
  4115. var attributeElement = getFirstChild(element);
  4116. attributeElement;
  4117. attributeElement = getNextSibling(attributeElement)
  4118. )
  4119. {
  4120. var tag = attributeElement.tagName.toLowerCase();
  4121. if ( tag == 'attribute' )
  4122. {
  4123. var attribute = new Attribute();
  4124. attribute.name = attributeElement.firstChild.nodeValue;
  4125. attribute.displayName = attributeElement.getAttribute('display');
  4126. if ( attributeElement.getAttribute('hrefBase') )
  4127. {
  4128. attribute.hrefBase = attributeElement.getAttribute('hrefBase');
  4129. }
  4130. if ( attributeElement.getAttribute('target') )
  4131. {
  4132. attribute.target = attributeElement.getAttribute('target');
  4133. }
  4134. if ( attribute.name == magnitudeName )
  4135. {
  4136. magnitudeIndex = attributes.length;
  4137. }
  4138. if ( attributeElement.getAttribute('listAll') )
  4139. {
  4140. attribute.listAll = attributeElement.getAttribute('listAll').toLowerCase();
  4141. }
  4142. else if ( attributeElement.getAttribute('listNode') )
  4143. {
  4144. attribute.listNode = attributeElement.getAttribute('listNode').toLowerCase();
  4145. }
  4146. else if ( attributeElement.getAttribute('dataAll') )
  4147. {
  4148. attribute.dataAll = attributeElement.getAttribute('dataAll').toLowerCase();
  4149. }
  4150. else if ( attributeElement.getAttribute('dataNode') )
  4151. {
  4152. attribute.dataNode = attributeElement.getAttribute('dataNode').toLowerCase();
  4153. }
  4154. if ( attributeElement.getAttribute('mono') )
  4155. {
  4156. attribute.mono = true;
  4157. }
  4158. attributes.push(attribute);
  4159. }
  4160. else if ( tag == 'list' )
  4161. {
  4162. var attribute = new Attribute();
  4163. attribute.name = attributeElement.firstChild.nodeValue;
  4164. attribute.list = true;
  4165. attributes.push(attribute);
  4166. }
  4167. else if ( tag == 'data' )
  4168. {
  4169. var attribute = new Attribute();
  4170. attribute.name = attributeElement.firstChild.nodeValue;
  4171. attribute.data = true;
  4172. attributes.push(attribute);
  4173. var enableScript = document.createElement('script');
  4174. var date = new Date();
  4175. enableScript.src =
  4176. attributeElement.getAttribute('enable') + '?' +
  4177. date.getTime();
  4178. document.body.appendChild(enableScript);
  4179. }
  4180. }
  4181. break;
  4182. case 'color':
  4183. hueName = element.getAttribute('attribute');
  4184. hueStart = Number(element.getAttribute('hueStart')) / 360;
  4185. hueEnd = Number(element.getAttribute('hueEnd')) / 360;
  4186. valueStart = Number(element.getAttribute('valueStart'));
  4187. valueEnd = Number(element.getAttribute('valueEnd'));
  4188. //
  4189. interpolateHue(hueStart, hueEnd, valueStart, valueEnd);
  4190. //
  4191. if ( element.getAttribute('default') == 'true' )
  4192. {
  4193. hueDefault = true;
  4194. }
  4195. break;
  4196. case 'datasets':
  4197. datasetNames = new Array();
  4198. //
  4199. for ( j = getFirstChild(element); j; j = getNextSibling(j) )
  4200. {
  4201. datasetNames.push(j.firstChild.nodeValue);
  4202. }
  4203. datasets = datasetNames.length;
  4204. break;
  4205. case 'node':
  4206. head = loadTreeDOM
  4207. (
  4208. element,
  4209. magnitudeName,
  4210. hueName,
  4211. hueStart,
  4212. hueEnd,
  4213. valueStart,
  4214. valueEnd
  4215. );
  4216. break;
  4217. }
  4218. }
  4219. // get GET options
  4220. //
  4221. var urlHalves = String(document.location).split('?');
  4222. var datasetDefault = 0;
  4223. var maxDepthDefault;
  4224. var nodeDefault = 0;
  4225. //
  4226. if ( urlHalves[1] )
  4227. {
  4228. var vars = urlHalves[1].split('&');
  4229. for ( i = 0; i < vars.length; i++ )
  4230. {
  4231. var pair = vars[i].split('=');
  4232. switch ( pair[0] )
  4233. {
  4234. case 'collapse':
  4235. collapse = pair[1] == 'true';
  4236. break;
  4237. case 'color':
  4238. hueDefault = pair[1] == 'true';
  4239. break;
  4240. case 'dataset':
  4241. datasetDefault = Number(pair[1]);
  4242. break;
  4243. case 'depth':
  4244. maxDepthDefault = Number(pair[1]) + 1;
  4245. break;
  4246. case 'key':
  4247. showKeys = pair[1] == 'true';
  4248. break;
  4249. case 'font':
  4250. fontSize = Number(pair[1]);
  4251. break;
  4252. case 'node':
  4253. nodeDefault = Number(pair[1]);
  4254. break;
  4255. default:
  4256. getVariables.push(pair[0] + '=' + pair[1]);
  4257. break;
  4258. }
  4259. }
  4260. }
  4261. addOptionElements(hueName, hueDefault);
  4262. setCallBacks();
  4263. head.sort();
  4264. maxAbsoluteDepth = 0;
  4265. selectDataset(datasetDefault);
  4266. if ( maxDepthDefault && maxDepthDefault < head.maxDepth )
  4267. {
  4268. maxAbsoluteDepth = maxDepthDefault;
  4269. }
  4270. else
  4271. {
  4272. maxAbsoluteDepth = head.maxDepth;
  4273. }
  4274. selectNode(nodes[nodeDefault]);
  4275. setInterval(update, 20);
  4276. window.onresize = handleResize;
  4277. updateMaxAbsoluteDepth();
  4278. updateViewNeeded = true;
  4279. }
  4280. function loadTreeDOM
  4281. (
  4282. domNode,
  4283. magnitudeName,
  4284. hueName,
  4285. hueStart,
  4286. hueEnd,
  4287. valueStart,
  4288. valueEnd
  4289. )
  4290. {
  4291. var newNode = new Node();
  4292. newNode.name = domNode.getAttribute('name');
  4293. if ( domNode.getAttribute('href') )
  4294. {
  4295. newNode.href = domNode.getAttribute('href');
  4296. }
  4297. if ( hueName )
  4298. {
  4299. newNode.hues = new Array();
  4300. }
  4301. for ( var i = getFirstChild(domNode); i; i = getNextSibling(i) )
  4302. {
  4303. switch ( i.tagName.toLowerCase() )
  4304. {
  4305. case 'node':
  4306. var newChild = loadTreeDOM
  4307. (
  4308. i,
  4309. magnitudeName,
  4310. hueName,
  4311. hueStart,
  4312. hueEnd,
  4313. valueStart,
  4314. valueEnd
  4315. );
  4316. newChild.parent = newNode;
  4317. newNode.children.push(newChild);
  4318. break;
  4319. default:
  4320. var attributeName = i.tagName.toLowerCase();
  4321. var index = attributeIndex(attributeName);
  4322. //
  4323. newNode.attributes[index] = new Array();
  4324. //
  4325. for ( var j = getFirstChild(i); j; j = getNextSibling(j) )
  4326. {
  4327. if ( attributes[index] == undefined )
  4328. {
  4329. var x = 5;
  4330. }
  4331. if ( attributes[index].list )
  4332. {
  4333. newNode.attributes[index].push(new Array());
  4334. for ( var k = getFirstChild(j); k; k = getNextSibling(k) )
  4335. {
  4336. newNode.attributes[index][newNode.attributes[index].length - 1].push(k.firstChild.nodeValue);
  4337. }
  4338. }
  4339. else
  4340. {
  4341. var value = j.firstChild ? j.firstChild.nodeValue : '';
  4342. if ( j.getAttribute('href') )
  4343. {
  4344. var target;
  4345. if ( attributes[index].target )
  4346. {
  4347. target = ' target="' + attributes[index].target + '"';
  4348. }
  4349. value = '<a href="' + attributes[index].hrefBase + j.getAttribute('href') + '"' + target + '>' + value + '</a>';
  4350. }
  4351. newNode.attributes[index].push(value);
  4352. }
  4353. }
  4354. //
  4355. if ( attributeName == magnitudeName || attributeName == hueName )
  4356. {
  4357. for ( j = 0; j < datasets; j++ )
  4358. {
  4359. var value = newNode.attributes[index][j] == undefined ? 0 : Number(newNode.attributes[index][j]);
  4360. newNode.attributes[index][j] = value;
  4361. if ( attributeName == hueName )
  4362. {
  4363. var hue = lerp
  4364. (
  4365. value,
  4366. valueStart,
  4367. valueEnd,
  4368. hueStart,
  4369. hueEnd
  4370. );
  4371. if ( hue < hueStart == hueStart < hueEnd )
  4372. {
  4373. hue = hueStart;
  4374. }
  4375. else if ( hue > hueEnd == hueStart < hueEnd )
  4376. {
  4377. hue = hueEnd;
  4378. }
  4379. newNode.hues[j] = hue;
  4380. }
  4381. }
  4382. if ( attributeName == hueName )
  4383. {
  4384. newNode.hue = new Tween(newNode.hues[0], newNode.hues[0]);
  4385. }
  4386. }
  4387. break;
  4388. }
  4389. }
  4390. return newNode;
  4391. }
  4392. function maxAbsoluteDepthDecrease()
  4393. {
  4394. if ( maxAbsoluteDepth > 2 )
  4395. {
  4396. maxAbsoluteDepth--;
  4397. head.setMaxDepths();
  4398. handleResize();
  4399. }
  4400. }
  4401. function maxAbsoluteDepthIncrease()
  4402. {
  4403. if ( maxAbsoluteDepth < head.maxDepth )
  4404. {
  4405. maxAbsoluteDepth++;
  4406. head.setMaxDepths();
  4407. handleResize();
  4408. }
  4409. }
  4410. function measureText(text, bold)
  4411. {
  4412. context.font = bold ? fontBold : fontNormal;
  4413. var dim = context.measureText(text);
  4414. return dim.width;
  4415. }
  4416. function min(a, b)
  4417. {
  4418. return a < b ? a : b;
  4419. }
  4420. function minWidth()
  4421. {
  4422. // Min wedge width (at center) for displaying a node (or for displaying a
  4423. // label if it's at the highest level being viewed, multiplied by 2 to make
  4424. // further calculations simpler
  4425. return (fontSize * 2.3);
  4426. }
  4427. function mouseMove(e)
  4428. {
  4429. mouseX = e.pageX;
  4430. mouseY = e.pageY - headerHeight;
  4431. if ( head && ! quickLook )
  4432. {
  4433. checkHighlight();
  4434. }
  4435. }
  4436. function mouseClick(e)
  4437. {
  4438. if ( highlightedNode == focusNode && focusNode != selectedNode || selectedNode.hasParent(highlightedNode) )
  4439. {
  4440. if ( highlightedNode.hasChildren() )
  4441. {
  4442. expand(highlightedNode);
  4443. }
  4444. }
  4445. else if ( progress == 1 )//( highlightedNode != selectedNode )
  4446. {
  4447. setFocus(highlightedNode);
  4448. // document.body.style.cursor='ew-resize';
  4449. draw();
  4450. checkHighlight();
  4451. var date = new Date();
  4452. mouseDownTime = date.getTime();
  4453. mouseDown = true;
  4454. }
  4455. }
  4456. function mouseUp(e)
  4457. {
  4458. if ( quickLook )
  4459. {
  4460. navigateBack();
  4461. quickLook = false;
  4462. }
  4463. mouseDown = false;
  4464. }
  4465. function navigateBack()
  4466. {
  4467. if ( nodeHistoryPosition > 0 )
  4468. {
  4469. nodeHistory[nodeHistoryPosition] = selectedNode;
  4470. nodeHistoryPosition--;
  4471. if ( nodeHistory[nodeHistoryPosition].collapse )
  4472. {
  4473. collapseCheckBox.checked = collapse = false;
  4474. }
  4475. setSelectedNode(nodeHistory[nodeHistoryPosition]);
  4476. updateDatasetButtons();
  4477. updateView();
  4478. }
  4479. }
  4480. function navigateUp()
  4481. {
  4482. if ( selectedNode.getParent() )
  4483. {
  4484. selectNode(selectedNode.getParent());
  4485. updateView();
  4486. }
  4487. }
  4488. function navigateForward()
  4489. {
  4490. if ( nodeHistoryPosition < nodeHistory.length - 1 )
  4491. {
  4492. nodeHistoryPosition++;
  4493. var newNode = nodeHistory[nodeHistoryPosition];
  4494. if ( newNode.collapse )
  4495. {
  4496. collapseCheckBox.checked = collapse = false;
  4497. }
  4498. if ( nodeHistoryPosition == nodeHistory.length - 1 )
  4499. {
  4500. // this will ensure the forward button is disabled
  4501. nodeHistory.length = nodeHistoryPosition;
  4502. }
  4503. setSelectedNode(newNode);
  4504. updateDatasetButtons();
  4505. updateView();
  4506. }
  4507. }
  4508. function nextDataset()
  4509. {
  4510. var newDataset = currentDataset;
  4511. do
  4512. {
  4513. if ( newDataset == datasets - 1 )
  4514. {
  4515. newDataset = 0;
  4516. }
  4517. else
  4518. {
  4519. newDataset++;
  4520. }
  4521. }
  4522. while ( datasetDropDown.options[newDataset].disabled )
  4523. selectDataset(newDataset);
  4524. }
  4525. function onDatasetChange()
  4526. {
  4527. selectDataset(datasetDropDown.selectedIndex);
  4528. }
  4529. function onKeyDown(event)
  4530. {
  4531. if
  4532. (
  4533. event.keyCode == 37 &&
  4534. document.activeElement.id != 'search' &&
  4535. document.activeElement.id != 'linkText'
  4536. )
  4537. {
  4538. navigateBack();
  4539. event.preventDefault();
  4540. }
  4541. else if
  4542. (
  4543. event.keyCode == 39 &&
  4544. document.activeElement.id != 'search' &&
  4545. document.activeElement.id != 'linkText'
  4546. )
  4547. {
  4548. navigateForward();
  4549. event.preventDefault();
  4550. }
  4551. else if ( event.keyCode == 38 && datasets > 1 )
  4552. {
  4553. prevDataset();
  4554. //if ( document.activeElement.id == 'datasets' )
  4555. {
  4556. event.preventDefault();
  4557. }
  4558. }
  4559. else if ( event.keyCode == 40 && datasets > 1 )
  4560. {
  4561. nextDataset();
  4562. //if ( document.activeElement.id == 'datasets' )
  4563. {
  4564. event.preventDefault();
  4565. }
  4566. }
  4567. else if ( event.keyCode == 9 && datasets > 1 )
  4568. {
  4569. selectLastDataset();
  4570. event.preventDefault();
  4571. }
  4572. else if ( event.keyCode == 83 )
  4573. {
  4574. progress += .01;
  4575. }
  4576. else if ( event.keyCode == 66 )
  4577. {
  4578. progress -= .01;
  4579. }
  4580. else if ( event.keyCode == 70 )
  4581. {
  4582. progress = 1;
  4583. }
  4584. }
  4585. function onKeyUp(event)
  4586. {
  4587. if ( event.keyCode == 27 && document.activeElement.id == 'search' )
  4588. {
  4589. search.value = '';
  4590. onSearchChange();
  4591. }
  4592. }
  4593. function onSearchChange()
  4594. {
  4595. nSearchResults = 0;
  4596. head.search();
  4597. if ( search.value == '' )
  4598. {
  4599. searchResults.innerHTML = '';
  4600. }
  4601. else
  4602. {
  4603. searchResults.innerHTML = nSearchResults + ' results';
  4604. }
  4605. setFocus(selectedNode);
  4606. draw();
  4607. }
  4608. function prevDataset()
  4609. {
  4610. var newDataset = currentDataset;
  4611. do
  4612. {
  4613. if ( newDataset == 0 )
  4614. {
  4615. newDataset = datasets - 1;
  4616. }
  4617. else
  4618. {
  4619. newDataset--;
  4620. }
  4621. }
  4622. while ( datasetDropDown.options[newDataset].disabled );
  4623. selectDataset(newDataset);
  4624. }
  4625. function resetKeyOffset()
  4626. {
  4627. currentKey = 1;
  4628. keyMinTextLeft = centerX + gRadius + buffer - buffer / (keys + 1) / 2 + fontSize / 2;
  4629. keyMinAngle = 0;
  4630. }
  4631. function rgbText(r, g, b)
  4632. {
  4633. var rgbArray =
  4634. [
  4635. "rgb(",
  4636. Math.floor(r),
  4637. ",",
  4638. Math.floor(g),
  4639. ",",
  4640. Math.floor(b),
  4641. ")"
  4642. ];
  4643. return rgbArray.join('');
  4644. }
  4645. function round(number)
  4646. {
  4647. if ( number >= 1 || number <= -1 )
  4648. {
  4649. return number.toFixed(0);
  4650. }
  4651. else
  4652. {
  4653. return number.toPrecision(1);
  4654. }
  4655. }
  4656. function roundedRectangle(x, y, width, height, radius)
  4657. {
  4658. if ( radius * 2 > width )
  4659. {
  4660. radius = width / 2;
  4661. }
  4662. if ( radius * 2 > height )
  4663. {
  4664. radius = height / 2;
  4665. }
  4666. context.beginPath();
  4667. context.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2, false);
  4668. context.lineTo(x + width - radius, y);
  4669. context.arc(x + width - radius, y + radius, radius, Math.PI * 3 / 2, Math.PI * 2, false);
  4670. context.lineTo(x + width, y + height - radius);
  4671. context.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2, false);
  4672. context.lineTo(x + radius, y + height);
  4673. context.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI, false);
  4674. context.lineTo(x, y + radius);
  4675. }
  4676. function passClick(e)
  4677. {
  4678. mouseClick(e);
  4679. }
  4680. function searchResultString(results)
  4681. {
  4682. var searchResults = this.searchResults;
  4683. if ( this.isSearchResult )
  4684. {
  4685. // don't count ourselves
  4686. searchResults--;
  4687. }
  4688. return ' - ' + results + (results > 1 ? ' results' : ' result');
  4689. }
  4690. function setCallBacks()
  4691. {
  4692. canvas.onselectstart = function(){return false;} // prevent unwanted highlighting
  4693. document.onmousemove = mouseMove;
  4694. window.onblur = focusLost;
  4695. window.onmouseout = focusLost;
  4696. document.onkeyup = onKeyUp;
  4697. document.onkeydown = onKeyDown;
  4698. canvas.onmousedown = mouseClick;
  4699. document.onmouseup = mouseUp;
  4700. keyControl.onclick = toggleKeys;
  4701. collapseCheckBox = document.getElementById('collapse');
  4702. collapseCheckBox.checked = collapse;
  4703. collapseCheckBox.onclick = handleResize;
  4704. maxAbsoluteDepthText = document.getElementById('maxAbsoluteDepth');
  4705. maxAbsoluteDepthButtonDecrease = document.getElementById('maxAbsoluteDepthDecrease');
  4706. maxAbsoluteDepthButtonIncrease = document.getElementById('maxAbsoluteDepthIncrease');
  4707. maxAbsoluteDepthButtonDecrease.onclick = maxAbsoluteDepthDecrease;
  4708. maxAbsoluteDepthButtonIncrease.onclick = maxAbsoluteDepthIncrease;
  4709. fontSizeText = document.getElementById('fontSize');
  4710. fontSizeButtonDecrease = document.getElementById('fontSizeDecrease');
  4711. fontSizeButtonIncrease = document.getElementById('fontSizeIncrease');
  4712. fontSizeButtonDecrease.onclick = fontSizeDecrease;
  4713. fontSizeButtonIncrease.onclick = fontSizeIncrease;
  4714. maxAbsoluteDepth = 0;
  4715. backButton = document.getElementById('back');
  4716. backButton.onclick = navigateBack;
  4717. forwardButton = document.getElementById('forward');
  4718. forwardButton.onclick = navigateForward;
  4719. snapshotButton = document.getElementById('snapshot');
  4720. snapshotButton.onclick = snapshot;
  4721. // details = document.getElementById('details');
  4722. detailsName = document.getElementById('detailsName');
  4723. detailsExpand = document.getElementById('detailsExpand');
  4724. detailsInfo = document.getElementById('detailsInfo');
  4725. search = document.getElementById('search');
  4726. search.onkeyup = onSearchChange;
  4727. searchResults = document.getElementById('searchResults');
  4728. useHueDiv = document.getElementById('useHueDiv');
  4729. linkButton = document.getElementById('linkButton');
  4730. linkButton.onclick = showLink;
  4731. linkText = document.getElementById('linkText');
  4732. linkText.onblur = hideLink;
  4733. hide(linkText);
  4734. image = document.getElementById('hiddenImage');
  4735. if ( image.complete )
  4736. {
  4737. hiddenPattern = context.createPattern(image, 'repeat');
  4738. }
  4739. else
  4740. {
  4741. image.onload = function()
  4742. {
  4743. hiddenPattern = context.createPattern(image, 'repeat');
  4744. }
  4745. }
  4746. var loadingImageElement = document.getElementById('loadingImage');
  4747. if ( loadingImageElement )
  4748. {
  4749. loadingImage = loadingImageElement.src;
  4750. }
  4751. }
  4752. function selectDataset(newDataset)
  4753. {
  4754. lastDataset = currentDataset;
  4755. currentDataset = newDataset
  4756. if ( datasets > 1 )
  4757. {
  4758. datasetDropDown.selectedIndex = currentDataset;
  4759. updateDatasetButtons();
  4760. datasetAlpha.start = 1.5;
  4761. datasetChanged = true;
  4762. }
  4763. head.setMagnitudes(0);
  4764. head.setDepth(1, 1);
  4765. head.setMaxDepths();
  4766. handleResize();
  4767. }
  4768. function selectLastDataset()
  4769. {
  4770. selectDataset(lastDataset);
  4771. handleResize();
  4772. }
  4773. function selectNode(newNode)
  4774. {
  4775. if ( selectedNode != newNode )
  4776. {
  4777. // truncate history at current location to create a new branch
  4778. //
  4779. nodeHistory.length = nodeHistoryPosition;
  4780. if ( selectedNode != 0 )
  4781. {
  4782. nodeHistory.push(selectedNode);
  4783. nodeHistoryPosition++;
  4784. }
  4785. setSelectedNode(newNode);
  4786. //updateView();
  4787. }
  4788. updateDatasetButtons();
  4789. }
  4790. function setFocus(node)
  4791. {
  4792. if ( node == focusNode )
  4793. {
  4794. // return;
  4795. }
  4796. focusNode = node;
  4797. if ( node.href )
  4798. {
  4799. detailsName.innerHTML =
  4800. '<a target="_blank" href="' + node.href + '">' + node.name + '</a>';
  4801. }
  4802. else
  4803. {
  4804. detailsName.innerHTML = node.name;
  4805. }
  4806. var table = '<table>';
  4807. table += '<tr><td></td></tr>';
  4808. for ( var i = 0; i < node.attributes.length; i++ )
  4809. {
  4810. if ( attributes[i].displayName && node.attributes[i] != undefined )
  4811. {
  4812. var index = node.attributes[i].length == 1 && attributes[i].mono ? 0 : currentDataset;
  4813. if ( node.attributes[i][index] != undefined && node.attributes[i][currentDataset] != '' )
  4814. {
  4815. var value = node.attributes[i][index];
  4816. if ( attributes[i].listNode != undefined )
  4817. {
  4818. value =
  4819. '<a href="" onclick="showList(' +
  4820. attributeIndex(attributes[i].listNode) + ',' + i +
  4821. ',false);return false;" title="Show list">' +
  4822. value + '</a>';
  4823. }
  4824. else if ( attributes[i].listAll != undefined )
  4825. {
  4826. value =
  4827. '<a href="" onclick="showList(' +
  4828. attributeIndex(attributes[i].listAll) + ',' + i +
  4829. ',true);return false;" title="Show list">' +
  4830. value + '</a>';
  4831. }
  4832. else if ( attributes[i].dataNode != undefined && dataEnabled )
  4833. {
  4834. value =
  4835. '<a href="" onclick="showData(' +
  4836. attributeIndex(attributes[i].dataNode) + ',' + i +
  4837. ',false);return false;" title="Show data">' +
  4838. value + '</a>';
  4839. }
  4840. else if ( attributes[i].dataAll != undefined && dataEnabled )
  4841. {
  4842. value =
  4843. '<a href="" onclick="showData(' +
  4844. attributeIndex(attributes[i].dataAll) + ',' + i +
  4845. ',true);return false;" title="Show data">' +
  4846. value + '</a>';
  4847. }
  4848. table +=
  4849. '<tr><td><strong>' + attributes[i].displayName + ':</strong></td><td>' +
  4850. value + '</td></tr>';
  4851. }
  4852. }
  4853. }
  4854. table += '</table>';
  4855. detailsInfo.innerHTML = table;
  4856. detailsExpand.disabled = !focusNode.hasChildren() || focusNode == selectedNode;
  4857. }
  4858. function setSelectedNode(newNode)
  4859. {
  4860. if ( selectedNode && selectedNode.hasParent(newNode) )
  4861. {
  4862. zoomOut = true;
  4863. }
  4864. else
  4865. {
  4866. zoomOut = false;
  4867. }
  4868. selectedNodeLast = selectedNode;
  4869. selectedNode = newNode;
  4870. if ( focusNode != selectedNode )
  4871. {
  4872. setFocus(selectedNode);
  4873. }
  4874. }
  4875. function waitForData(dataWindow, target, title, time)
  4876. {
  4877. if ( nodeData.length == target )
  4878. {
  4879. var data = nodeData.join('');
  4880. dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading'));
  4881. document.body.removeChild(document.getElementById('data'));
  4882. if ( true || navigator.appName == 'Microsoft Internet Explorer' ) // :(
  4883. {
  4884. dataWindow.document.open();
  4885. dataWindow.document.write('<pre>' + data + '</pre>');
  4886. dataWindow.document.close();
  4887. }
  4888. else
  4889. {
  4890. var pre = document.createElement('pre');
  4891. dataWindow.document.body.appendChild(pre);
  4892. pre.innerHTML = data;
  4893. }
  4894. dataWindow.document.title = title; // replace after document.write()
  4895. }
  4896. else
  4897. {
  4898. var date = new Date();
  4899. if ( date.getTime() - time > 10000 )
  4900. {
  4901. dataWindow.document.body.removeChild(dataWindow.document.getElementById('loading'));
  4902. document.body.removeChild(document.getElementById('data'));
  4903. dataWindow.document.body.innerHTML =
  4904. 'Timed out loading supplemental files for:<br/>' + document.location;
  4905. }
  4906. else
  4907. {
  4908. setTimeout(function() {waitForData(dataWindow, target, title, time);}, 100);
  4909. }
  4910. }
  4911. }
  4912. function data(newData)
  4913. {
  4914. nodeData.push(newData);
  4915. }
  4916. function enableData()
  4917. {
  4918. dataEnabled = true;
  4919. }
  4920. function showData(indexData, indexAttribute, summary)
  4921. {
  4922. var dataWindow = window.open('', '_blank');
  4923. var title = 'Krona - ' + attributes[indexAttribute].displayName + ' - ' + focusNode.name;
  4924. dataWindow.document.title = title;
  4925. nodeData = new Array();
  4926. if ( dataWindow && dataWindow.document && dataWindow.document.body != null )
  4927. {
  4928. //var loadImage = document.createElement('img');
  4929. //loadImage.src = "file://localhost/Users/ondovb/Krona/KronaTools/img/loading.gif";
  4930. //loadImage.id = "loading";
  4931. //loadImage.alt = "Loading...";
  4932. //dataWindow.document.body.appendChild(loadImage);
  4933. dataWindow.document.body.innerHTML =
  4934. '<img id="loading" src="' + loadingImage + '" alt="Loading..."></img>';
  4935. }
  4936. var scripts = document.createElement('div');
  4937. scripts.id = 'data';
  4938. document.body.appendChild(scripts);
  4939. var files = focusNode.getData(indexData, summary);
  4940. var date = new Date();
  4941. var time = date.getTime();
  4942. for ( var i = 0; i < files.length; i++ )
  4943. {
  4944. var script = document.createElement('script');
  4945. script.src = files[i] + '?' + time;
  4946. scripts.appendChild(script);
  4947. }
  4948. waitForData(dataWindow, files.length, title, time);
  4949. return false;
  4950. }
  4951. function showList(indexList, indexAttribute, summary)
  4952. {
  4953. var list = focusNode.getList(indexList, summary).join('\n');
  4954. var dataWindow = window.open('', '_blank');
  4955. if ( true || navigator.appName == 'Microsoft Internet Explorer' ) // :(
  4956. {
  4957. dataWindow.document.open();
  4958. dataWindow.document.write('<pre>' + list + '</pre>');
  4959. dataWindow.document.close();
  4960. }
  4961. else
  4962. {
  4963. var pre = document.createElement('pre');
  4964. dataWindow.document.body.appendChild(pre);
  4965. pre.innerHTML = list;
  4966. }
  4967. dataWindow.document.title = 'Krona - ' + attributes[indexAttribute].displayName + ' - ' + focusNode.name;
  4968. }
  4969. function snapshot()
  4970. {
  4971. svg = svgHeader();
  4972. resetKeyOffset();
  4973. snapshotMode = true;
  4974. selectedNode.draw(false, true);
  4975. selectedNode.draw(true, true);
  4976. if ( focusNode != 0 && focusNode != selectedNode )
  4977. {
  4978. context.globalAlpha = 1;
  4979. focusNode.drawHighlight(true);
  4980. }
  4981. if ( hueDisplayName && useHue() )
  4982. {
  4983. drawLegendSVG();
  4984. }
  4985. snapshotMode = false;
  4986. svg += svgFooter();
  4987. snapshotWindow = window.open
  4988. (
  4989. 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg),
  4990. '_blank'
  4991. );
  4992. /* var data = window.open('data:text/plain;charset=utf-8,hello', '_blank');
  4993. var data = window.open('', '_blank');
  4994. data.document.open('text/plain');
  4995. data.document.write('hello');
  4996. data.document.close();
  4997. var button = document.createElement('input');
  4998. button.type = 'button';
  4999. button.value = 'save';
  5000. button.onclick = save;
  5001. data.document.body.appendChild(button);
  5002. // snapshotWindow.document.write(svg);
  5003. // snapshotWindow.document.close();
  5004. */
  5005. }
  5006. function save()
  5007. {
  5008. alert(document.body.innerHTML);
  5009. }
  5010. function spacer()
  5011. {
  5012. if ( snapshotMode )
  5013. {
  5014. return '&#160;&#160;&#160;';
  5015. }
  5016. else
  5017. {
  5018. return ' ';
  5019. }
  5020. }
  5021. function svgFooter()
  5022. {
  5023. return '</svg>';
  5024. }
  5025. function svgHeader()
  5026. {
  5027. var patternWidth = fontSize * .6;//radius / 50;
  5028. return '\
  5029. <?xml version="1.0" standalone="no"?>\
  5030. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \
  5031. "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\
  5032. <svg width="' + imageWidth + '" height="' + imageHeight + '" version="1.1"\
  5033. xmlns="http://www.w3.org/2000/svg">\
  5034. <title>Krona (snapshot) - ' +
  5035. (datasets > 1 ? datasetNames[currentDataset] + ' - ' : '') + selectedNode.name +
  5036. '</title>\
  5037. <defs>\
  5038. <style type="text/css">\
  5039. text {font-size: ' + fontSize + 'px; font-family: ' + fontFamily + '; dominant-baseline:central}\
  5040. path {stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
  5041. path.wedge {stroke:none}\
  5042. path.line {fill:none;stroke:black;}\
  5043. line {stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
  5044. line.tick {stroke-width:' + thinLineWidth * fontSize / 6 + ';}\
  5045. line.pattern {stroke-width:' + thinLineWidth * fontSize / 18 + ';}\
  5046. circle {fill:none;stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
  5047. rect {stroke:black;stroke-width:' + thinLineWidth * fontSize / 12 + ';}\
  5048. .highlight {stroke:black;stroke-width:'+ highlightLineWidth * fontSize / 12 + ';}\
  5049. .searchHighlight {fill:rgb(255, 255, 100);stroke:none;}\
  5050. </style>\
  5051. <pattern id="hiddenPattern" patternUnits="userSpaceOnUse" \
  5052. x="0" y="0" width="' + patternWidth + '" height="' + patternWidth + '">\
  5053. <line class="pattern" x1="0" y1="0" x2="' + patternWidth / 2 + '" y2="' + patternWidth / 2 + '"/>\
  5054. <line class="pattern" x1="' + patternWidth / 2 + '" y1="' + patternWidth +
  5055. '" x2="' + patternWidth + '" y2="' + patternWidth / 2 + '"/>\
  5056. </pattern>\
  5057. </defs>\
  5058. ';
  5059. }
  5060. function svgText(text, x, y, anchor, bold)
  5061. {
  5062. if ( typeof(anchor) == 'undefined' )
  5063. {
  5064. anchor = 'start';
  5065. }
  5066. return '<text x="' + x + '" y="' + y +
  5067. '" style="font-weight:' + (bold ? 'bold' : 'normal') +
  5068. '" text-anchor="' + anchor + '">' + text + '</text>';
  5069. }
  5070. function toggleKeys()
  5071. {
  5072. if ( showKeys )
  5073. {
  5074. keyControl.value = 'â&#x20AC;?';
  5075. showKeys = false;
  5076. }
  5077. else
  5078. {
  5079. keyControl.value = 'x';
  5080. showKeys = true;
  5081. }
  5082. updateKeyControl();
  5083. if ( progress == 1 )
  5084. {
  5085. draw();
  5086. }
  5087. }
  5088. function update()
  5089. {
  5090. if ( ! head )
  5091. {
  5092. return;
  5093. }
  5094. if ( mouseDown && focusNode != selectedNode )
  5095. {
  5096. var date = new Date();
  5097. if ( date.getTime() - mouseDownTime > quickLookHoldLength )
  5098. {
  5099. if ( focusNode.hasChildren() )
  5100. {
  5101. expand(focusNode);
  5102. quickLook = true;
  5103. }
  5104. }
  5105. }
  5106. if ( updateViewNeeded )
  5107. {
  5108. resize();
  5109. mouseX = -1;
  5110. mouseY = -1;
  5111. collapse = collapseCheckBox.checked;
  5112. compress = true;//compressCheckBox.checked;
  5113. shorten = true;//shortenCheckBox.checked;
  5114. checkSelectedCollapse();
  5115. updateMaxAbsoluteDepth();
  5116. if ( focusNode.getCollapse() || focusNode.depth > maxAbsoluteDepth )
  5117. {
  5118. setFocus(selectedNode);
  5119. }
  5120. else
  5121. {
  5122. setFocus(focusNode);
  5123. }
  5124. updateView();
  5125. updateViewNeeded = false;
  5126. }
  5127. var date = new Date();
  5128. progress = (date.getTime() - tweenStartTime) / tweenLength;
  5129. // progress += .01;
  5130. if ( progress >= 1 )
  5131. {
  5132. progress = 1;
  5133. }
  5134. if ( progress != progressLast )
  5135. {
  5136. tweenFactor =
  5137. (1 / (1 + Math.exp(-tweenCurvature * (progress - .5))) - .5) /
  5138. (tweenMax - .5) / 2 + .5;
  5139. if ( progress == 1 )
  5140. {
  5141. snapshotButton.disabled = false;
  5142. zoomOut = false;
  5143. //updateKeyControl();
  5144. if ( ! quickLook )
  5145. {
  5146. //checkHighlight();
  5147. }
  5148. if ( fpsDisplay )
  5149. {
  5150. fpsDisplay.innerHTML = 'fps: ' + Math.round(tweenFrames * 1000 / tweenLength);
  5151. }
  5152. }
  5153. draw();
  5154. }
  5155. progressLast = progress;
  5156. }
  5157. function updateDatasetButtons()
  5158. {
  5159. if ( datasets == 1 )
  5160. {
  5161. return;
  5162. }
  5163. var node = selectedNode ? selectedNode : head;
  5164. datasetButtonLast.disabled =
  5165. node.attributes[magnitudeIndex][lastDataset] == 0;
  5166. datasetButtonPrev.disabled = true;
  5167. datasetButtonNext.disabled = true;
  5168. for ( var i = 0; i < datasets; i++ )
  5169. {
  5170. var disable = node.attributes[magnitudeIndex][i] == 0;
  5171. datasetDropDown.options[i].disabled = disable;
  5172. if ( ! disable )
  5173. {
  5174. if ( i != currentDataset )
  5175. {
  5176. datasetButtonPrev.disabled = false;
  5177. datasetButtonNext.disabled = false;
  5178. }
  5179. }
  5180. }
  5181. }
  5182. function updateDatasetWidths()
  5183. {
  5184. if ( datasets > 1 )
  5185. {
  5186. for ( var i = 0; i < datasets; i++ )
  5187. {
  5188. context.font = fontBold;
  5189. var dim = context.measureText(datasetNames[i]);
  5190. datasetWidths[i] = dim.width;
  5191. }
  5192. }
  5193. }
  5194. function updateKeyControl()
  5195. {
  5196. if ( keys == 0 )//|| progress != 1 )
  5197. {
  5198. keyControl.style.visibility = 'hidden';
  5199. }
  5200. else
  5201. {
  5202. keyControl.style.visibility = 'visible';
  5203. keyControl.style.right = margin + 'px';
  5204. if ( showKeys )
  5205. {
  5206. keyControl.style.top =
  5207. imageHeight -
  5208. (
  5209. keys * (keySize + keyBuffer) -
  5210. keyBuffer +
  5211. margin +
  5212. keyControl.clientHeight * 1.5
  5213. ) + 'px';
  5214. }
  5215. else
  5216. {
  5217. keyControl.style.top =
  5218. (imageHeight - margin - keyControl.clientHeight) + 'px';
  5219. }
  5220. }
  5221. }
  5222. function updateView()
  5223. {
  5224. if ( selectedNode.depth > maxAbsoluteDepth - 1 )
  5225. {
  5226. maxAbsoluteDepth = selectedNode.depth + 1;
  5227. }
  5228. highlightedNode = selectedNode;
  5229. angleFactor = 2 * Math.PI / (selectedNode.magnitude);
  5230. maxPossibleDepth = Math.floor(gRadius / (fontSize * minRingWidthFactor));
  5231. if ( maxPossibleDepth < 4 )
  5232. {
  5233. maxPossibleDepth = 4;
  5234. }
  5235. var minRadiusInner = fontSize * 8 / gRadius;
  5236. var minRadiusFirst = fontSize * 6 / gRadius;
  5237. var minRadiusOuter = fontSize * 5 / gRadius;
  5238. if ( .25 < minRadiusInner )
  5239. {
  5240. minRadiusInner = .25;
  5241. }
  5242. if ( .15 < minRadiusFirst )
  5243. {
  5244. minRadiusFirst = .15;
  5245. }
  5246. if ( .15 < minRadiusOuter )
  5247. {
  5248. minRadiusOuter = .15;
  5249. }
  5250. // visibility of nodes depends on the depth they are displayed at,
  5251. // so we need to set the max depth assuming they can all be displayed
  5252. // and iterate it down based on the deepest child node we can display
  5253. //
  5254. var maxDepth;
  5255. var newMaxDepth = selectedNode.getMaxDepth() - selectedNode.getDepth() + 1;
  5256. //
  5257. do
  5258. {
  5259. maxDepth = newMaxDepth;
  5260. if ( ! compress && maxDepth > maxPossibleDepth )
  5261. {
  5262. maxDepth = maxPossibleDepth;
  5263. }
  5264. if ( compress )
  5265. {
  5266. compressedRadii = new Array(maxDepth);
  5267. compressedRadii[0] = minRadiusInner;
  5268. var offset = 0;
  5269. while
  5270. (
  5271. lerp
  5272. (
  5273. Math.atan(offset + 2),
  5274. Math.atan(offset + 1),
  5275. Math.atan(maxDepth + offset - 1),
  5276. minRadiusInner,
  5277. 1 - minRadiusOuter
  5278. ) - minRadiusInner > minRadiusFirst &&
  5279. offset < 10
  5280. )
  5281. {
  5282. offset++;
  5283. }
  5284. offset--;
  5285. for ( var i = 1; i < maxDepth; i++ )
  5286. {
  5287. compressedRadii[i] = lerp
  5288. (
  5289. Math.atan(i + offset),
  5290. Math.atan(offset),
  5291. Math.atan(maxDepth + offset - 1),
  5292. minRadiusInner,
  5293. 1 - minRadiusOuter
  5294. )
  5295. }
  5296. }
  5297. else
  5298. {
  5299. nodeRadius = 1 / maxDepth;
  5300. }
  5301. newMaxDepth = selectedNode.maxVisibleDepth(maxDepth);
  5302. if ( compress )
  5303. {
  5304. if ( newMaxDepth <= maxPossibleDepth )
  5305. {
  5306. // compress
  5307. }
  5308. }
  5309. else
  5310. {
  5311. if ( newMaxDepth > maxPossibleDepth )
  5312. {
  5313. newMaxDepth = maxPossibleDepth;
  5314. }
  5315. }
  5316. }
  5317. while ( newMaxDepth < maxDepth );
  5318. maxDisplayDepth = maxDepth;
  5319. lightnessFactor = (lightnessMax - lightnessBase) / (maxDepth > 8 ? 8 : maxDepth);
  5320. keys = 0;
  5321. nLabelOffsets = new Array(maxDisplayDepth - 1);
  5322. labelOffsets = new Array(maxDisplayDepth - 1);
  5323. labelLastNodes = new Array(maxDisplayDepth - 1);
  5324. labelFirstNodes = new Array(maxDisplayDepth - 1);
  5325. for ( var i = 0; i < maxDisplayDepth - 1; i++ )
  5326. {
  5327. if ( compress )
  5328. {
  5329. if ( i == maxDisplayDepth - 1 )
  5330. {
  5331. nLabelOffsets[i] = 0;
  5332. }
  5333. else
  5334. {
  5335. var width =
  5336. (compressedRadii[i + 1] - compressedRadii[i]) *
  5337. gRadius;
  5338. nLabelOffsets[i] = Math.floor(width / fontSize / 1.2);
  5339. if ( nLabelOffsets[i] > 2 )
  5340. {
  5341. nLabelOffsets[i] = min
  5342. (
  5343. Math.floor(width / fontSize / 1.75),
  5344. 5
  5345. );
  5346. }
  5347. }
  5348. }
  5349. else
  5350. {
  5351. nLabelOffsets[i] = Math.max
  5352. (
  5353. Math.floor(Math.sqrt((nodeRadius * gRadius / fontSize)) * 1.5),
  5354. 3
  5355. );
  5356. }
  5357. labelOffsets[i] = Math.floor((nLabelOffsets[i] - 1) / 2);
  5358. labelLastNodes[i] = new Array(nLabelOffsets[i] + 1);
  5359. labelFirstNodes[i] = new Array(nLabelOffsets[i] + 1);
  5360. for ( var j = 0; j <= nLabelOffsets[i]; j++ )
  5361. {
  5362. // these arrays will allow nodes with neighboring labels to link to
  5363. // each other to determine max label length
  5364. labelLastNodes[i][j] = 0;
  5365. labelFirstNodes[i][j] = 0;
  5366. }
  5367. }
  5368. fontSizeText.innerHTML = fontSize;
  5369. fontNormal = fontSize + 'px ' + fontFamily;
  5370. context.font = fontNormal;
  5371. fontBold = 'bold ' + fontSize + 'px ' + fontFamily;
  5372. tickLength = fontSize * .7;
  5373. head.setTargets(0);
  5374. keySize = ((imageHeight - margin * 3) * 1 / 2) / keys * 3 / 4;
  5375. if ( keySize > fontSize * maxKeySizeFactor )
  5376. {
  5377. keySize = fontSize * maxKeySizeFactor;
  5378. }
  5379. keyBuffer = keySize / 3;
  5380. fontSizeLast = fontSize;
  5381. if ( datasetChanged )
  5382. {
  5383. datasetChanged = false;
  5384. }
  5385. else
  5386. {
  5387. datasetAlpha.start = 0;
  5388. }
  5389. var date = new Date();
  5390. tweenStartTime = date.getTime();
  5391. progress = 0;
  5392. tweenFrames = 0;
  5393. updateKeyControl();
  5394. updateDatasetWidths();
  5395. document.title = 'Krona - ' + selectedNode.name;
  5396. updateNavigationButtons();
  5397. snapshotButton.disabled = true;
  5398. maxAbsoluteDepthText.innerHTML = maxAbsoluteDepth - 1;
  5399. maxAbsoluteDepthButtonDecrease.disabled = (maxAbsoluteDepth == 2);
  5400. maxAbsoluteDepthButtonIncrease.disabled = (maxAbsoluteDepth == head.maxDepth);
  5401. if ( collapse != collapseLast && search.value != '' )
  5402. {
  5403. onSearchChange();
  5404. collapseLast = collapse;
  5405. }
  5406. }
  5407. function updateMaxAbsoluteDepth()
  5408. {
  5409. while ( selectedNode.depth > maxAbsoluteDepth - 1 )
  5410. {
  5411. selectedNode = selectedNode.getParent();
  5412. }
  5413. }
  5414. function updateNavigationButtons()
  5415. {
  5416. backButton.disabled = (nodeHistoryPosition == 0);
  5417. // upButton.disabled = (selectedNode.getParent() == 0);
  5418. forwardButton.disabled = (nodeHistoryPosition == nodeHistory.length);
  5419. }
  5420. function useHue()
  5421. {
  5422. return useHueCheckBox && useHueCheckBox.checked;
  5423. }
  5424. /*
  5425. function zoomOut()
  5426. {
  5427. return (
  5428. selectedNodeLast != 0 &&
  5429. selectedNodeLast.getDepth() < selectedNode.getDepth());
  5430. }
  5431. */