PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/treeview/static/js/treelib.js

https://github.com/bendmorris/phylocommons
JavaScript | 2182 lines | 2118 code | 33 blank | 31 comment | 7 complexity | 7e67e649ff9a909c4544356fb466c2d9 MD5 | raw file

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

  1. /**
  2. *
  3. * Javascript library to display phylogenetic trees
  4. *
  5. */
  6. var VIEWER_WIDTH = 600;
  7. var VIEWER_HEIGHT = 500;
  8. var ZOOM_IN = 1.33;
  9. var ZOOM_OUT = 0.75;
  10. var errors = ['syntax', 'missing parenthesis', 'unbalanced parentheses', 'stack not empty'];
  11. //--------------------------------------------------------------------------------------------------
  12. // http://stackoverflow.com/questions/3019278/any-way-to-specify-the-base-of-math-log-in-javascript
  13. function log10(val) {
  14. return Math.log(val) / Math.LN10;
  15. }
  16. // http://stackoverflow.com/questions/387707/whats-the-best-way-to-define-a-class-in-javascript
  17. //--------------------------------------------------------------------------------------------------
  18. // http://stackoverflow.com/questions/1303646/check-whether-variable-is-number-or-string-in-javascript
  19. function isNumber (o) {
  20. return ! isNaN (o-0);
  21. }
  22. //--------------------------------------------------------------------------------------------------
  23. function ctype_alnum (str)
  24. {
  25. return (str.match(/^[a-z0-9]+$/i) != null);
  26. }
  27. //--------------------------------------------------------------------------------------------------
  28. function linePath(p0, p1)
  29. {
  30. var path = 'M ' + p0['x'] + ' ' + p0['y'] + ' ' + p1['x'] + ' ' + p1['y'];
  31. return path;
  32. }
  33. //--------------------------------------------------------------------------------------------------
  34. function drawLine(svg_id, p0, p1)
  35. {
  36. var line = document.createElementNS('http://www.w3.org/2000/svg','path');
  37. //newLine.setAttribute('id','node' + p.id);
  38. line.setAttribute('vector-effect','non-scaling-stroke');
  39. line.setAttribute('style','stroke:black;stroke-width:1;');
  40. line.setAttribute('d', linePath(p0, p1));
  41. var svg = document.getElementById(svg_id);
  42. svg.appendChild(line);
  43. }
  44. //--------------------------------------------------------------------------------------------------
  45. function drawText(svg_id, p, string)
  46. {
  47. var text = document.createElementNS('http://www.w3.org/2000/svg','text');
  48. //newLine.setAttribute('id','node' + p.id);
  49. text.setAttribute('style','alignment-baseline:middle');
  50. text.setAttribute('x', p['x']);
  51. text.setAttribute('y', p['y']);
  52. var textNode=document.createTextNode(string)
  53. text.appendChild(textNode);
  54. var svg = document.getElementById(svg_id);
  55. svg.appendChild(text);
  56. }
  57. //--------------------------------------------------------------------------------------------------
  58. function drawRotatedText(svg_id, p, string, angle, align)
  59. {
  60. var text = document.createElementNS('http://www.w3.org/2000/svg','text');
  61. //newLine.setAttribute('id','node' + p.id);
  62. text.setAttribute('style','alignment-baseline:middle');
  63. text.setAttribute('x', p['x']);
  64. text.setAttribute('y', p['y']);
  65. switch (align)
  66. {
  67. case 'left':
  68. text.setAttribute('text-anchor', 'start');
  69. break;
  70. case 'centre':
  71. case 'center':
  72. text.setAttribute('text-anchor', 'middle');
  73. break;
  74. case 'right':
  75. text.setAttribute('text-anchor', 'end');
  76. break;
  77. default:
  78. text.setAttribute('text-anchor', 'start');
  79. break;
  80. }
  81. if (angle != 0)
  82. {
  83. text.setAttribute('transform', 'rotate(' + angle + ' ' + p['x'] + ' ' + p['y'] + ')');
  84. }
  85. var textNode=document.createTextNode(string)
  86. text.appendChild(textNode);
  87. var svg = document.getElementById(svg_id);
  88. svg.appendChild(text);
  89. }
  90. //--------------------------------------------------------------------------------------------------
  91. function circeArcPath(p0, p1, radius, large_arc_flag)
  92. {
  93. var path = 'M '
  94. + p0['x'] + ' ' + p0['y']
  95. + ' A ' + radius + ' ' + radius
  96. + ' 0 ';
  97. if (large_arc_flag)
  98. {
  99. path += ' 1 ';
  100. }
  101. else
  102. {
  103. path += ' 0 ';
  104. }
  105. path += ' 1 '
  106. + p1['x'] + ' ' + p1['y'] ;
  107. return path;
  108. }
  109. //--------------------------------------------------------------------------------------------------
  110. function drawCircleArc(svg_id, p0, p1, radius, large_arc_flag)
  111. {
  112. var arc = document.createElementNS('http://www.w3.org/2000/svg','path');
  113. arc.setAttribute('vector-effect','non-scaling-stroke');
  114. arc.setAttribute('style','stroke:black;stroke-width:1;');
  115. arc.setAttribute('fill','none');
  116. var path = circeArcPath(p0, p1, radius, large_arc_flag);
  117. arc.setAttribute('d', path)
  118. var svg = document.getElementById(svg_id);
  119. svg.appendChild(arc);
  120. }
  121. //--------------------------------------------------------------------------------------------------
  122. function drawPath(svg_id, pathString)
  123. {
  124. var path = document.createElementNS('http://www.w3.org/2000/svg','path');
  125. //newLine.setAttribute('id','node' + p.id);
  126. path.setAttribute('vector-effect','non-scaling-stroke');
  127. path.setAttribute('style','stroke:blue;stroke-width:1;');
  128. path.setAttribute('d', pathString);
  129. var svg = document.getElementById(svg_id);
  130. svg.appendChild(path);
  131. }
  132. //--------------------------------------------------------------------------------------------------
  133. // Remove NEXUS-style string formatting, e.g. underscores
  134. function formatString(s)
  135. {
  136. s = s.replace(/_/g, ' ');
  137. return s;
  138. }
  139. //--------------------------------------------------------------------------------------------------
  140. // http://stackoverflow.com/questions/894860/set-a-default-parameter-value-for-a-javascript-function
  141. function Node(label)
  142. {
  143. this.ancestor = null;
  144. this.child = null;
  145. this.sibling = null;
  146. this.label = typeof label !== 'undefined' ? label : '';
  147. this.id = 0;
  148. this.weight = 0;
  149. this.xy = [];
  150. this.edge_length = 0.0;
  151. this.path_length = 0.0;
  152. this.depth = 0;
  153. }
  154. //--------------------------------------------------------------------------------------------------
  155. Node.prototype.IsLeaf = function()
  156. {
  157. return (!this.child);
  158. }
  159. //--------------------------------------------------------------------------------------------------
  160. Node.prototype.GetRightMostSibling = function()
  161. {
  162. var p = this;
  163. while (p.sibling)
  164. {
  165. p = p.sibling;
  166. }
  167. return p;
  168. }
  169. //--------------------------------------------------------------------------------------------------
  170. function Tree()
  171. {
  172. this.root = null;
  173. this.num_leaves = 0;
  174. this.num_nodes = 0;
  175. this.label_to_node_map = [];
  176. this.nodes = [];
  177. this.rooted = true;
  178. this.has_edge_lengths = false;
  179. this.error = 0;
  180. }
  181. //--------------------------------------------------------------------------------------------------
  182. Tree.prototype.NewNode = function(label)
  183. {
  184. var node = new Node(label);
  185. node.id = this.num_nodes++;
  186. this.nodes[node.id] = node;
  187. if (typeof label !== undefined)
  188. {
  189. this.label_to_node_map[label] = node.id;
  190. }
  191. return node;
  192. }
  193. //--------------------------------------------------------------------------------------------------
  194. Tree.prototype.Parse = function(str)
  195. {
  196. str = str.replace('"', "");
  197. // Strip NEXUS-style comments
  198. str = str.replace(/\[[^\[]+\]/g, "");
  199. str = str.replace(/\(/g, "|(|");
  200. str = str.replace(/\)/g, "|)|");
  201. str = str.replace(/,/g, "|,|");
  202. str = str.replace(/:/g, "|:|");
  203. str = str.replace(/;/g, "|;|");
  204. str = str.replace(/\|\|/g, "|");
  205. str = str.replace(/^\|/, "");
  206. str = str.replace(/\|$/, "");
  207. //console.log(str);
  208. var token = str.split("|");
  209. var curnode = this.NewNode();
  210. this.root = curnode;
  211. var state = 0;
  212. var stack = [];
  213. var i = 0;
  214. var q = null;
  215. this.error = 0;
  216. while ((state != 99) && (this.error == 0))
  217. {
  218. switch (state)
  219. {
  220. case 0:
  221. if (ctype_alnum(token[i].charAt(0)))
  222. {
  223. this.num_leaves++;
  224. label = token[i];
  225. // to do: KML
  226. curnode.label = label;
  227. this.label_to_node_map[label] = curnode;
  228. i++;
  229. state = 1;
  230. }
  231. else
  232. {
  233. if (token[i].charAt(0) == "'")
  234. {
  235. label = token[i];
  236. label = label.replace(/^'/, "");
  237. label = label.replace(/'$/, "");
  238. this.num_leaves++;
  239. // to do: KML
  240. curnode.label = label;
  241. this.label_to_node_map[label] = curnode;
  242. i++;
  243. state = 1;
  244. }
  245. else
  246. {
  247. switch (token[i])
  248. {
  249. case '(':
  250. state = 2;
  251. break;
  252. default:
  253. state = 99;
  254. this.error = 1; // syntax
  255. break;
  256. }
  257. }
  258. }
  259. break;
  260. case 1: // getinternode
  261. switch (token[i])
  262. {
  263. case ':':
  264. case ',':
  265. case ')':
  266. state = 2;
  267. break;
  268. default:
  269. state = 99;
  270. this.error = 1; // syntax
  271. break;
  272. }
  273. break;
  274. case 2: // nextmove
  275. switch (token[i])
  276. {
  277. case ':':
  278. i++;
  279. if (isNumber(token[i]))
  280. {
  281. curnode.edge_length = parseFloat(token[i]);
  282. this.has_edge_lengths = true;
  283. i++;
  284. }
  285. break;
  286. case ',':
  287. q = this.NewNode();
  288. curnode.sibling = q;
  289. var c = stack.length;
  290. if (c == 0)
  291. {
  292. state = 99;
  293. this.error = 2; // missing (
  294. }
  295. else
  296. {
  297. q.ancestor = stack[c - 1];
  298. curnode = q;
  299. state = 0;
  300. i++;
  301. }
  302. break;
  303. case '(':
  304. stack.push(curnode);
  305. q = this.NewNode();
  306. curnode.child = q;
  307. q.ancestor = curnode;
  308. curnode = q;
  309. state = 0;
  310. i++;
  311. break;
  312. case ')':
  313. if (stack.length == 0)
  314. {
  315. state = 99;
  316. this.error = 3; // unbalanced
  317. }
  318. else
  319. {
  320. curnode = stack.pop();
  321. state = 3;
  322. i++;
  323. }
  324. break;
  325. case ';':
  326. if (stack.length == 0)
  327. {
  328. state = 99;
  329. }
  330. else
  331. {
  332. state = 99;
  333. this.error = 4; // stack not empty
  334. }
  335. break;
  336. default:
  337. state = 99;
  338. this.error = 1; // syntax
  339. break;
  340. }
  341. break;
  342. case 3: // finishchildren
  343. if (ctype_alnum(token[i].charAt(0)))
  344. {
  345. curnode.label = token[i];
  346. this.label_to_node_map[token[i]] = curnode;
  347. i++;
  348. }
  349. else
  350. {
  351. switch (token[i])
  352. {
  353. case ':':
  354. i++;
  355. if (isNumber(token[i]))
  356. {
  357. curnode.edge_length = parseFloat(token[i]);
  358. this.has_edge_lengths = true;
  359. i++;
  360. }
  361. break;
  362. case ')':
  363. if (stack.length == 0)
  364. {
  365. state = 99;
  366. this.error = 3; // unbalanced
  367. }
  368. else
  369. {
  370. curnode = stack.pop();
  371. i++;
  372. }
  373. break;
  374. case ',':
  375. q = this.NewNode();
  376. curnode.sibling = q;
  377. if (stack.length == 0)
  378. {
  379. state = 99;
  380. this.error = 2; // missing (
  381. }
  382. else
  383. {
  384. q.ancestor = stack[stack.length - 1];
  385. curnode = q;
  386. state = 0;
  387. i++;
  388. }
  389. break;
  390. case ';':
  391. state = 2;
  392. break;
  393. default:
  394. state = 99;
  395. this.error = 1; // syntax
  396. break;
  397. }
  398. }
  399. break;
  400. }
  401. }
  402. }
  403. //--------------------------------------------------------------------------------------------------
  404. Tree.prototype.ComputeWeights = function(p)
  405. {
  406. if (p)
  407. {
  408. p.weight = 0;
  409. this.ComputeWeights(p.child);
  410. this.ComputeWeights(p.sibling);
  411. if (p.IsLeaf())
  412. {
  413. p.weight = 1;
  414. }
  415. if (p.ancestor)
  416. {
  417. p.ancestor.weight += p.weight;
  418. }
  419. }
  420. }
  421. //--------------------------------------------------------------------------------------------------
  422. Tree.prototype.ComputeDepths = function()
  423. {
  424. for (var i in this.nodes)
  425. {
  426. if (this.nodes[i].IsLeaf())
  427. {
  428. p = this.nodes[i].ancestor;
  429. var count = 1;
  430. while (p)
  431. {
  432. p.depth = Math.max(p.depth, count);
  433. count++;
  434. p = p.ancestor;
  435. }
  436. }
  437. }
  438. }
  439. //--------------------------------------------------------------------------------------------------
  440. Tree.prototype.WriteNewick = function()
  441. {
  442. var newick = '';
  443. var stack = [];
  444. var curnode = this.root;
  445. while (curnode)
  446. {
  447. //console.log(curnode.label);
  448. if (curnode.child)
  449. {
  450. newick += '(';
  451. stack.push(curnode);
  452. curnode = curnode.child;
  453. }
  454. else
  455. {
  456. newick += curnode.label;
  457. var length = curnode.edge_length;
  458. if (length)
  459. {
  460. newick += ':' + length;
  461. }
  462. while (stack.length > 0 && curnode.sibling == null)
  463. {
  464. newick += ')';
  465. curnode = stack.pop();
  466. // internal node label and length
  467. if (typeof curnode.label !== undefined)
  468. {
  469. newick += curnode.label;
  470. }
  471. var length = curnode.edge_length;
  472. if (length)
  473. {
  474. newick += ':' + length;
  475. }
  476. }
  477. if (stack.length == 0)
  478. {
  479. curnode = null;
  480. }
  481. else
  482. {
  483. newick += ',';
  484. curnode = curnode.sibling;
  485. }
  486. }
  487. }
  488. newick += ';';
  489. return newick;
  490. //console.log(newick);
  491. }
  492. //--------------------------------------------------------------------------------------------------
  493. function NodeIterator(root)
  494. {
  495. this.root = root;
  496. this.cur = null;
  497. this.stack = [];
  498. }
  499. //--------------------------------------------------------------------------------------------------
  500. NodeIterator.prototype.Begin = function()
  501. {
  502. this.cur = this.root;
  503. while (this.cur.child)
  504. {
  505. this.stack.push(this.cur);
  506. this.cur = this.cur.child;
  507. }
  508. return this.cur;
  509. }
  510. //--------------------------------------------------------------------------------------------------
  511. NodeIterator.prototype.Next = function()
  512. {
  513. if (this.stack.length == 0)
  514. {
  515. this.cur = null;
  516. }
  517. else
  518. {
  519. if (this.cur.sibling)
  520. {
  521. var p = this.cur.sibling;
  522. while (p.child)
  523. {
  524. this.stack.push(p);
  525. p = p.child;
  526. }
  527. this.cur = p;
  528. }
  529. else
  530. {
  531. this.cur = this.stack.pop();
  532. }
  533. }
  534. return this.cur;
  535. }
  536. //--------------------------------------------------------------------------------------------------
  537. PreorderIterator.prototype = new NodeIterator;
  538. function PreorderIterator()
  539. {
  540. NodeIterator.apply(this, arguments)
  541. };
  542. //--------------------------------------------------------------------------------------------------
  543. PreorderIterator.prototype.Begin = function()
  544. {
  545. this.cur = this.root;
  546. return this.cur;
  547. }
  548. //--------------------------------------------------------------------------------------------------
  549. PreorderIterator.prototype.Next = function()
  550. {
  551. if (this.cur.child)
  552. {
  553. this.stack.push(this.cur);
  554. this.cur = this.cur.child;
  555. }
  556. else
  557. {
  558. while (this.stack.length > 0 && this.cur.sibling == null)
  559. {
  560. this.cur = this.stack.pop();
  561. }
  562. if (this.stack.length == 0)
  563. {
  564. this.cur = null;
  565. }
  566. else
  567. {
  568. this.cur = this.cur.sibling;
  569. }
  570. }
  571. return this.cur;
  572. }
  573. //--------------------------------------------------------------------------------------------------
  574. function TreeDrawer()
  575. {
  576. //this.t = tree;
  577. this.leaf_count = 0;
  578. this.leaf_gap = 0;
  579. this.node_gap = 0;
  580. this.last_y = 0;
  581. this.svg_id;
  582. this.draw_scale_bar = false;
  583. }
  584. //--------------------------------------------------------------------------------------------------
  585. TreeDrawer.prototype.Init = function(tree, settings)
  586. {
  587. this.t = tree;
  588. // defaults
  589. this.settings = settings;
  590. this.left = 0;
  591. this.top = 0;
  592. /*
  593. if (this.settings.fontHeight)
  594. {
  595. this.top += this.settings.fontHeight/2.0;
  596. this.settings.height -= this.settings.fontHeight;
  597. }
  598. */
  599. }
  600. //--------------------------------------------------------------------------------------------------
  601. TreeDrawer.prototype.CalcInternal = function(p)
  602. {
  603. var pt = [];
  604. pt['x'] = this.left + this.node_gap * (this.t.num_leaves - p.weight);
  605. pt['y'] = this.last_y - ((p.weight - 1) * this.leaf_gap)/2;
  606. p.xy = pt;
  607. }
  608. //--------------------------------------------------------------------------------------------------
  609. TreeDrawer.prototype.CalcLeaf = function(p)
  610. {
  611. var pt = [];
  612. pt['y'] = this.top + (this.leaf_count * this.leaf_gap);
  613. this.last_y = pt['y'];
  614. this.leaf_count++;
  615. // slanted cladogram
  616. pt['x'] = this.left + this.settings.width;
  617. p.xy = pt;
  618. }
  619. //--------------------------------------------------------------------------------------------------
  620. TreeDrawer.prototype.CalcNodeGap = function()
  621. {
  622. if (this.t.rooted)
  623. {
  624. this.node_gap = this.settings.width / this.t.num_leaves;
  625. this.left += this.node_gap;
  626. this.settings.width -= this.node_gap;
  627. }
  628. else
  629. {
  630. this.node_gap = this.settings.width / (this.t.num_leaves - 1);
  631. }
  632. }
  633. //--------------------------------------------------------------------------------------------------
  634. TreeDrawer.prototype.CalcCoordinates = function()
  635. {
  636. this.t.ComputeWeights(this.t.root);
  637. this.leaf_count = 0;
  638. this.leaf_gap = this.settings.height/(this.t.num_leaves - 1);
  639. this.CalcNodeGap();
  640. var n = new NodeIterator(this.t.root);
  641. var q = n.Begin();
  642. while (q != null)
  643. {
  644. if (q.IsLeaf())
  645. {
  646. this.CalcLeaf(q);
  647. }
  648. else
  649. {
  650. this.CalcInternal(q);
  651. }
  652. q = n.Next();
  653. }
  654. }
  655. //--------------------------------------------------------------------------------------------------
  656. TreeDrawer.prototype.DrawLeaf = function(p)
  657. {
  658. var p0 = p.xy
  659. var anc = p.ancestor;
  660. if (anc)
  661. {
  662. var p1 = anc.xy;
  663. drawLine(this.settings.svg_id, p0, p1);
  664. }
  665. }
  666. //--------------------------------------------------------------------------------------------------
  667. TreeDrawer.prototype.DrawInternal = function(p)
  668. {
  669. var p0 = p.xy
  670. var anc = p.ancestor;
  671. if (anc)
  672. {
  673. var p1 = anc.xy;
  674. drawLine(this.settings.svg_id, p0, p1);
  675. }
  676. }
  677. //--------------------------------------------------------------------------------------------------
  678. TreeDrawer.prototype.DrawRoot = function()
  679. {
  680. var p0 = this.t.root.xy
  681. var p1 = [];
  682. p1['x'] = p0['x'];
  683. p1['y'] = p0['y'];
  684. p1['x'] -= this.node_gap;
  685. drawLine(this.settings.svg_id, p0, p1);
  686. }
  687. //--------------------------------------------------------------------------------------------------
  688. TreeDrawer.prototype.Draw = function()
  689. {
  690. var n = new NodeIterator(this.t.root);
  691. var q = n.Begin();
  692. while (q != null)
  693. {
  694. if (q.IsLeaf())
  695. {
  696. this.DrawLeaf(q);
  697. }
  698. else
  699. {
  700. this.DrawInternal(q);
  701. }
  702. q = n.Next();
  703. }
  704. if (this.t.rooted)
  705. {
  706. this.DrawRoot();
  707. }
  708. }
  709. //--------------------------------------------------------------------------------------------------
  710. TreeDrawer.prototype.DrawLabels = function(nexus)
  711. {
  712. var nxs = typeof nexus !== 'undefined' ? nexus : null;
  713. var n = new NodeIterator(this.t.root);
  714. var q = n.Begin();
  715. while (q != null)
  716. {
  717. if (q.IsLeaf())
  718. {
  719. var label = q.label;
  720. if (nxs)
  721. {
  722. if (nxs.treesblock.translate)
  723. {
  724. if (nxs.treesblock.translate[label])
  725. {
  726. label = nxs.treesblock.translate[label];
  727. }
  728. }
  729. }
  730. // offset
  731. label_xy = q.xy;
  732. label_xy['x'] += this.settings.fontHeight/2.0;
  733. drawText('viewport', label_xy, formatString(label));
  734. }
  735. q = n.Next();
  736. }
  737. }
  738. //--------------------------------------------------------------------------------------------------
  739. RectangleTreeDrawer.prototype = new TreeDrawer();
  740. function RectangleTreeDrawer()
  741. {
  742. TreeDrawer.apply(this, arguments);
  743. this.max_depth = 0;
  744. };
  745. //--------------------------------------------------------------------------------------------------
  746. RectangleTreeDrawer.prototype.CalcInternal = function(p)
  747. {
  748. var pt = [];
  749. pt['x'] = this.left + this.node_gap * (this.t.root.depth - p.depth);
  750. var pl = p.child.xy;
  751. var pr = p.child.GetRightMostSibling().xy;
  752. pt['y'] = pl['y'] + (pr['y'] - pl['y'])/2;
  753. p.xy['x'] = pt['x'];
  754. p.xy['y'] = pt['y'];
  755. }
  756. //--------------------------------------------------------------------------------------------------
  757. RectangleTreeDrawer.prototype.CalcNodeGap = function()
  758. {
  759. this.t.ComputeDepths();
  760. //console.log(this.t.root.depth);
  761. if (this.t.rooted)
  762. {
  763. this.node_gap = this.settings.width / (this.t.root.depth + 1);
  764. this.left += this.node_gap;
  765. this.settings.width -= this.node_gap;
  766. }
  767. else
  768. {
  769. this.node_gap = this.settings.width / this.t.root.depth;
  770. }
  771. }
  772. //--------------------------------------------------------------------------------------------------
  773. RectangleTreeDrawer.prototype.DrawLeaf = function(p)
  774. {
  775. var p0 = p.xy
  776. var p1 = [];
  777. var anc = p.ancestor;
  778. if (anc)
  779. {
  780. p1['x'] = anc.xy['x'];
  781. p1['y'] = p0['y'];
  782. drawLine(this.settings.svg_id, p0, p1);
  783. }
  784. }
  785. //--------------------------------------------------------------------------------------------------
  786. RectangleTreeDrawer.prototype.DrawInternal = function(p)
  787. {
  788. var p0 = [];
  789. var p1 = [];
  790. p0['x'] = p.xy['x'];
  791. p0['y'] = p.xy['y'];
  792. var anc = p.ancestor;
  793. if (anc)
  794. {
  795. p1['x'] = anc.xy['x'];
  796. p1['y'] = p0['y'];
  797. drawLine(this.settings.svg_id, p0, p1);
  798. }
  799. // vertical line
  800. var pl = p.child.xy;
  801. var pr = p.child.GetRightMostSibling().xy;
  802. p0['x'] = p0['x'];
  803. p0['y'] = pl['y'];
  804. p1['x'] = p0['x'];
  805. p1['y'] = pr['y'];
  806. drawLine(this.settings.svg_id, p0, p1);
  807. }
  808. //--------------------------------------------------------------------------------------------------
  809. PhylogramTreeDrawer.prototype = new RectangleTreeDrawer();
  810. function PhylogramTreeDrawer()
  811. {
  812. RectangleTreeDrawer.apply(this, arguments);
  813. this.max_path_length = 0;
  814. this.draw_scale_bar = true;
  815. };
  816. //--------------------------------------------------------------------------------------------------
  817. PhylogramTreeDrawer.prototype.CalcInternal = function(p)
  818. {
  819. var pt = [];
  820. pt['x'] = this.left + (p.path_length / this.max_path_length) * this.settings.width;
  821. var pl = p.child.xy;
  822. var pr = p.child.GetRightMostSibling().xy;
  823. pt['y'] = pl['y'] + (pr['y'] - pl['y'])/2;
  824. p.xy['x'] = pt['x'];
  825. p.xy['y'] = pt['y'];
  826. }
  827. //--------------------------------------------------------------------------------------------------
  828. PhylogramTreeDrawer.prototype.CalcLeaf = function(p)
  829. {
  830. var pt = [];
  831. pt['x'] = this.left + (p.path_length / this.max_path_length) * this.settings.width;
  832. pt['y'] = this.top + (this.leaf_count * this.leaf_gap);
  833. this.last_y = pt['y'];
  834. this.leaf_count++;
  835. p.xy['x'] = pt['x'];
  836. p.xy['y'] = pt['y'];
  837. }
  838. //--------------------------------------------------------------------------------------------------
  839. PhylogramTreeDrawer.prototype.CalcCoordinates = function()
  840. {
  841. this.max_path_length = 0;
  842. //console.log(this.max_path_length);
  843. this.t.root.path_length = this.t.root.edge_length;
  844. // build path lengths
  845. var n = new PreorderIterator(this.t.root);
  846. var q = n.Begin();
  847. while (q != null)
  848. {
  849. var d = q.edge_length;
  850. if (d < 0.00001)
  851. {
  852. d = 0.0;
  853. }
  854. if (q != this.t.root)
  855. {
  856. q.path_length = q.ancestor.path_length + d;
  857. }
  858. //console.log(q.label + ' ' + q.path_length + ' ' + q.edge_length);
  859. this.max_path_length = Math.max(this.max_path_length, q.path_length);
  860. q = n.Next();
  861. }
  862. //console.log(this.max_path_length);
  863. this.leaf_count = 0;
  864. this.leaf_gap = this.settings.height/(this.t.num_leaves - 1);
  865. n = new NodeIterator(this.t.root);
  866. var q = n.Begin();
  867. while (q != null)
  868. {
  869. if (q.IsLeaf())
  870. {
  871. this.CalcLeaf(q);
  872. }
  873. else
  874. {
  875. this.CalcInternal(q);
  876. }
  877. q = n.Next();
  878. }
  879. }
  880. //--------------------------------------------------------------------------------------------------
  881. PhylogramTreeDrawer.prototype.Draw = function()
  882. {
  883. // parent method
  884. RectangleTreeDrawer.prototype.Draw.call(this);
  885. // scale bar
  886. if (this.draw_scale_bar)
  887. {
  888. this.DrawScaleBar();
  889. }
  890. }
  891. //--------------------------------------------------------------------------------------------------
  892. PhylogramTreeDrawer.prototype.DrawScaleBar = function()
  893. {
  894. var p0 = [];
  895. var p1 = [];
  896. var m = log10(this.max_path_length);
  897. var i = Math.floor(m);
  898. var bar = Math.pow(10,i);
  899. var scalebar = (bar/this.max_path_length) * this.settings.width;
  900. p0['x'] = this.left;
  901. p0['y'] = this.top + this.settings.height + this.leaf_gap;
  902. p1['x'] = p0['x'] + scalebar;
  903. p1['y'] = p0['y'];
  904. drawLine(this.settings.svg_id, p0, p1);
  905. }
  906. //--------------------------------------------------------------------------------------------------
  907. CircleTreeDrawer.prototype = new RectangleTreeDrawer();
  908. function CircleTreeDrawer()
  909. {
  910. RectangleTreeDrawer.apply(this, arguments);
  911. this.leaf_angle = 0;
  912. this.leaf_radius = 0;
  913. this.max_path_length = 0;
  914. this.root_length = 0;
  915. };
  916. //--------------------------------------------------------------------------------------------------
  917. CircleTreeDrawer.prototype.CalcInternalRadius = function(p)
  918. {
  919. p.radius = this.node_gap * (this.t.root.depth - p.depth);
  920. }
  921. //--------------------------------------------------------------------------------------------------
  922. CircleTreeDrawer.prototype.CalcInternal = function(p)
  923. {
  924. var left_angle = p.child.angle;
  925. var right_angle = p.child.GetRightMostSibling().angle;
  926. p.angle = left_angle + (right_angle - left_angle)/2;
  927. this.CalcInternalRadius(p);
  928. var pt = [];
  929. pt['x'] = p.radius * Math.cos(p.angle);
  930. pt['y'] = p.radius * Math.sin(p.angle);
  931. p.xy['x'] = pt['x'];
  932. p.xy['y'] = pt['y'];
  933. var q = p.child;
  934. while (q)
  935. {
  936. pt = [];
  937. pt['x'] = p.radius * Math.cos(q.angle);
  938. pt['y'] = p.radius * Math.sin(q.angle);
  939. q.backarc = [];
  940. q.backarc['x'] = pt['x'];
  941. q.backarc['y'] = pt['y'];
  942. q = q.sibling;
  943. }
  944. }
  945. //--------------------------------------------------------------------------------------------------
  946. CircleTreeDrawer.prototype.CalcLeafRadius = function(p)
  947. {
  948. p.radius = this.leaf_radius;
  949. }
  950. //--------------------------------------------------------------------------------------------------
  951. CircleTreeDrawer.prototype.CalcLeaf = function(p)
  952. {
  953. p.angle = this.leaf_angle * this.leaf_count;
  954. this.leaf_count++;
  955. this.CalcLeafRadius(p);
  956. var pt = [];
  957. pt['x'] = p.radius * Math.cos(p.angle);
  958. pt['y'] = p.radius * Math.sin(p.angle);
  959. p.xy['x'] = pt['x'];
  960. p.xy['y'] = pt['y'];
  961. }
  962. //--------------------------------------------------------------------------------------------------
  963. CircleTreeDrawer.prototype.DrawLeaf = function(p)
  964. {
  965. var p0 = p.xy
  966. var p1 = p.backarc;
  967. drawLine(this.settings.svg_id, p0, p1);
  968. /*
  969. var p0 = p.xy
  970. var p1 = p.backarc;
  971. var path = linePath(p0, p1);
  972. var anc = p.ancestor;
  973. if (anc)
  974. {
  975. var p2 = anc.xy;
  976. var large_arc_flag = false;
  977. if (p.angle < anc.angle)
  978. {
  979. large_arc_flag = (Math.abs(anc.angle - p.angle) > Math.PI) ? true : false;
  980. path += circeArcPath(p1, p2, p.radius, large_arc_flag);
  981. }
  982. else
  983. {
  984. large_arc_flag = (Math.abs(p.angle - anc.angle) > Math.PI) ? true : false;
  985. path += circeArcPath(p2, p1, p.radius, large_arc_flag);
  986. }
  987. }
  988. drawPath(path);
  989. */
  990. }
  991. //--------------------------------------------------------------------------------------------------
  992. CircleTreeDrawer.prototype.DrawInternal = function(p)
  993. {
  994. var p0 = [];
  995. var p1 = [];
  996. p0['x'] = p.xy['x'];
  997. p0['y'] = p.xy['y'];
  998. var anc = p.ancestor;
  999. if (anc)
  1000. {
  1001. p0 = p.xy;
  1002. p1 = p.backarc;
  1003. drawLine(this.settings.svg_id, p0, p1);
  1004. }
  1005. // draw arc
  1006. p0 = p.child.backarc;
  1007. p1 = p.child.GetRightMostSibling().backarc;
  1008. var large_arc_flag = (Math.abs(p.child.GetRightMostSibling().angle - p.child.angle) > Math.PI) ? true : false;
  1009. drawCircleArc(this.settings.svg_id, p0, p1, p.radius, large_arc_flag);
  1010. /*
  1011. var anc = p.ancestor;
  1012. if (anc)
  1013. {
  1014. var p0 = p.xy
  1015. var p1 = p.backarc;
  1016. var path = '';//linePath(p0, p1);
  1017. var p2 = anc.xy;
  1018. var large_arc_flag = false; //(Math.abs(p.angle - anc.angle) > Math.PI) ? true : false;
  1019. if (p.angle < anc.angle)
  1020. {
  1021. path += circeArcPath(p1, p2, p.radius, large_arc_flag);
  1022. }
  1023. else
  1024. {
  1025. path += circeArcPath(p2, p1, p.radius, large_arc_flag);
  1026. }
  1027. drawPath(path);
  1028. }
  1029. */
  1030. }
  1031. //--------------------------------------------------------------------------------------------------
  1032. CircleTreeDrawer.prototype.DrawRoot = function()
  1033. {
  1034. var p0 = this.t.root.xy
  1035. var p1 = [];
  1036. p1['x'] = 0;
  1037. p1['y'] = 0;
  1038. drawLine(this.settings.svg_id, p0, p1);
  1039. }
  1040. //--------------------------------------------------------------------------------------------------
  1041. CircleTreeDrawer.prototype.CalcCoordinates = function()
  1042. {
  1043. this.t.ComputeDepths();
  1044. this.max_path_length = 0;
  1045. //console.log(this.max_path_length);
  1046. this.t.root.path_length = this.t.root.edge_length;
  1047. // build path lengths
  1048. var n = new PreorderIterator(this.t.root);
  1049. var q = n.Begin();
  1050. while (q != null)
  1051. {
  1052. var d = q.edge_length;
  1053. if (d < 0.00001)
  1054. {
  1055. d = 0.0;
  1056. }
  1057. if (q != this.t.root)
  1058. {
  1059. q.path_length = q.ancestor.path_length + d;
  1060. }
  1061. //console.log(q.label + ' ' + q.path_length + ' ' + q.edge_length);
  1062. this.max_path_length = Math.max(this.max_path_length, q.path_length);
  1063. q = n.Next();
  1064. }
  1065. this.leaf_count = 0;
  1066. this.leaf_angle = 2 * Math.PI / this.t.num_leaves;
  1067. this.leaf_radius = this.settings.width/2;
  1068. this.node_gap = this.leaf_radius / this.t.root.depth;
  1069. n = new NodeIterator(this.t.root);
  1070. var q = n.Begin();
  1071. while (q != null)
  1072. {
  1073. if (q.IsLeaf())
  1074. {
  1075. this.CalcLeaf(q);
  1076. }
  1077. else
  1078. {
  1079. this.CalcInternal(q);
  1080. }
  1081. q = n.Next();
  1082. }
  1083. }
  1084. //--------------------------------------------------------------------------------------------------
  1085. CircleTreeDrawer.prototype.Draw = function()
  1086. {
  1087. // parent method
  1088. TreeDrawer.prototype.Draw.call(this);
  1089. // move drawing to centre of viewport
  1090. var viewport = document.getElementById(this.settings.svg_id);
  1091. viewport.setAttribute('transform', 'translate(' + (this.settings.width + this.root_length)/2 + ' ' + this.settings.height/2 + ')');
  1092. }
  1093. //--------------------------------------------------------------------------------------------------
  1094. CircleTreeDrawer.prototype.DrawLabels = function(nexus)
  1095. {
  1096. var nxs = typeof nexus !== 'undefined' ? nexus : null;
  1097. var n = new NodeIterator(this.t.root);
  1098. var q = n.Begin();
  1099. while (q != null)
  1100. {
  1101. if (q.IsLeaf())
  1102. {
  1103. var label = q.label;
  1104. if (nxs)
  1105. {
  1106. if (nxs.treesblock.translate)
  1107. {
  1108. if (nxs.treesblock.translate[label])
  1109. {
  1110. label = nxs.treesblock.translate[label];
  1111. }
  1112. }
  1113. }
  1114. var align = 'left';
  1115. var angle = q.angle * 180.0/Math.PI;
  1116. if ((q.angle > Math.PI/2.0) && (q.angle < 1.5 * Math.PI))
  1117. {
  1118. align = 'right';
  1119. angle += 180.0;
  1120. }
  1121. // offset label
  1122. var r = q.radius + this.settings.fontHeight/2.0;
  1123. var label_xy = [];
  1124. label_xy['x'] = Math.cos(q.angle) * r;
  1125. label_xy['y'] = Math.sin(q.angle) * r;
  1126. drawRotatedText('viewport', label_xy, formatString(label), angle, align);
  1127. }
  1128. q = n.Next();
  1129. }
  1130. }
  1131. //--------------------------------------------------------------------------------------------------
  1132. CirclePhylogramDrawer.prototype = new CircleTreeDrawer();
  1133. function CirclePhylogramDrawer()
  1134. {
  1135. CircleTreeDrawer.apply(this, arguments)
  1136. this.max_path_length = 0;
  1137. this.draw_scale_bar = true;
  1138. };
  1139. //--------------------------------------------------------------------------------------------------
  1140. CirclePhylogramDrawer.prototype.CalcInternalRadius = function(p)
  1141. {
  1142. p.radius = this.root_length + (p.path_length / this.max_path_length) * (this.settings.width/2)
  1143. }
  1144. //--------------------------------------------------------------------------------------------------
  1145. CirclePhylogramDrawer.prototype.CalcLeafRadius = function(p)
  1146. {
  1147. p.radius = this.root_length + (p.path_length / this.max_path_length) * (this.settings.width/2)
  1148. }
  1149. //--------------------------------------------------------------------------------------------------
  1150. CirclePhylogramDrawer.prototype.CalcCoordinates = function()
  1151. {
  1152. this.max_path_length = 0;
  1153. //console.log(this.max_path_length);
  1154. if (this.settings.root_length)
  1155. {
  1156. this.root_length = this.settings.root_length * (this.settings.width/2);
  1157. this.settings.width -= 2 * this.root_length;
  1158. }
  1159. this.t.root.path_length = this.t.root.edge_length;
  1160. // build path lengths
  1161. var n = new PreorderIterator(this.t.root);
  1162. var q = n.Begin();
  1163. while (q != null)
  1164. {
  1165. var d = q.edge_length;
  1166. if (d < 0.00001)
  1167. {
  1168. d = 0.0;
  1169. }
  1170. if (q != this.t.root)
  1171. {
  1172. q.path_length = q.ancestor.path_length + d;
  1173. }
  1174. this.max_path_length = Math.max(this.max_path_length, q.path_length);
  1175. q = n.Next();
  1176. }
  1177. this.leaf_count = 0;
  1178. this.leaf_angle = 2 * Math.PI / this.t.num_leaves;
  1179. n = new NodeIterator(this.t.root);
  1180. var q = n.Begin();
  1181. while (q != null)
  1182. {
  1183. if (q.IsLeaf())
  1184. {
  1185. this.CalcLeaf(q);
  1186. }
  1187. else
  1188. {
  1189. this.CalcInternal(q);
  1190. }
  1191. q = n.Next();
  1192. }
  1193. }
  1194. //--------------------------------------------------------------------------------------------------
  1195. CirclePhylogramDrawer.prototype.Draw = function()
  1196. {
  1197. // parent method
  1198. TreeDrawer.prototype.Draw.call(this);
  1199. // move drawing to centre of viewport
  1200. var viewport = document.getElementById(this.settings.svg_id);
  1201. viewport.setAttribute('transform', 'translate(' + (this.settings.width + this.root_length)/2 + ' ' + this.settings.height/2 + ')');
  1202. }
  1203. //--------------------------------------------------------------------------------------------------
  1204. RadialTreeDrawer.prototype = new RectangleTreeDrawer();
  1205. function RadialTreeDrawer()
  1206. {
  1207. TreeDrawer.apply(this, arguments);
  1208. this.min_xy = [];
  1209. this.min_xy['x'] = 0;
  1210. this.min_xy['y'] = 0;
  1211. this.max_xy = [];
  1212. this.max_xy['x'] = 0;
  1213. this.max_xy['y'] = 0;
  1214. };
  1215. //--------------------------------------------------------------------------------------------------
  1216. RadialTreeDrawer.prototype.CalcInternal = function(p)
  1217. {
  1218. // angle of this node
  1219. var angle = p.angle;
  1220. // arc occupied by subtrees rooted at this node
  1221. var p_arc = 2 * Math.PI * (p.weight / this.t.num_leaves);
  1222. angle -= p_arc/2;
  1223. var q = p.child;
  1224. while (q)
  1225. {
  1226. var arc = 2 * Math.PI * (q.weight / this.t.num_leaves);
  1227. q.angle = arc/2 + angle;
  1228. var pt = [];
  1229. pt['x'] = p.xy['x'] + q.edge_length * Math.cos(q.angle) * 1000;
  1230. pt['y'] = p.xy['y'] + q.edge_length * Math.sin(q.angle) * 1000;
  1231. q.xy['x'] = pt['x'];
  1232. q.xy['y'] = pt['y'];
  1233. this.min_xy['x'] = Math.min(this.min_xy['x'], q.xy['x']);
  1234. this.min_xy['y'] = Math.min(this.min_xy['y'], q.xy['y']);
  1235. this.max_xy['x'] = Math.max(this.max_xy['x'], q.xy['x']);
  1236. this.max_xy['y'] = Math.max(this.max_xy['y'], q.xy['y']);
  1237. angle += arc;
  1238. q = q.sibling;
  1239. }
  1240. }
  1241. //--------------------------------------------------------------------------------------------------
  1242. RadialTreeDrawer.prototype.CalcLeaf = function(p)
  1243. {
  1244. }
  1245. //--------------------------------------------------------------------------------------------------
  1246. RadialTreeDrawer.prototype.DrawLeaf = function(p)
  1247. {
  1248. var p0 = p.xy;
  1249. var p1 = p.ancestor.xy;
  1250. drawLine(this.settings.svg_id, p0, p1);
  1251. }
  1252. //--------------------------------------------------------------------------------------------------
  1253. RadialTreeDrawer.prototype.DrawInternal = function(p)
  1254. {
  1255. var p0 = p.xy;
  1256. if (p.ancestor)
  1257. {
  1258. var p1 = p.ancestor.xy;
  1259. drawLine(this.settings.svg_id, p0, p1);
  1260. }
  1261. }
  1262. //--------------------------------------------------------------------------------------------------
  1263. RadialTreeDrawer.prototype.DrawRoot = function()
  1264. {
  1265. }
  1266. //--------------------------------------------------------------------------------------------------
  1267. RadialTreeDrawer.prototype.CalcCoordinates = function()
  1268. {
  1269. this.t.ComputeWeights(this.t.root);
  1270. // Generate unit branch lengths if tree has no branch lengths
  1271. if (!this.t.has_edge_lengths)
  1272. {
  1273. var n = new PreorderIterator(this.t.root);
  1274. var q = n.Begin();
  1275. while (q != null)
  1276. {
  1277. q.edge_length = 1.0;
  1278. q = n.Next();
  1279. }
  1280. }
  1281. this.leaf_angle = 2 * Math.PI / this.t.num_leaves;
  1282. this.t.root.angle = 0;
  1283. var pt = [];
  1284. pt['x'] = 0;
  1285. pt['y'] = 0;
  1286. this.t.root.xy['x'] = pt['x'];
  1287. this.t.root.xy['y'] = pt['y'];
  1288. n = new PreorderIterator(this.t.root);
  1289. var q = n.Begin();
  1290. while (q != null)
  1291. {
  1292. if (q.IsLeaf())
  1293. {
  1294. }
  1295. else
  1296. {
  1297. this.CalcInternal(q);
  1298. }
  1299. q = n.Next();
  1300. }
  1301. }
  1302. //--------------------------------------------------------------------------------------------------
  1303. RadialTreeDrawer.prototype.DrawLabels = function(nexus)
  1304. {
  1305. var nxs = typeof nexus !== 'undefined' ? nexus : null;
  1306. var n = new NodeIterator(this.t.root);
  1307. var q = n.Begin();
  1308. while (q != null)
  1309. {
  1310. if (q.IsLeaf())
  1311. {
  1312. var label = q.label;
  1313. if (nxs)
  1314. {
  1315. if (nxs.treesblock.translate)
  1316. {
  1317. if (nxs.treesblock.translate[label])
  1318. {
  1319. label = nxs.treesblock.translate[label];
  1320. }
  1321. }
  1322. }
  1323. var align = 'left';
  1324. var angle = q.angle * 180.0/Math.PI;
  1325. if ((q.angle < -Math.PI/2) || (q.angle > Math.PI/2))
  1326. {
  1327. align = 'right';
  1328. angle += 180.0;
  1329. }
  1330. var label_xy = [];
  1331. label_xy['x'] = q.xy['x'];
  1332. label_xy['y'] = q.xy['y'];
  1333. var offset = 10; //this.settings.fontHeight/2.0;
  1334. label_xy['x'] += Math.cos(q.angle) * offset;
  1335. label_xy['y'] += Math.sin(q.angle) * offset;
  1336. drawRotatedText('viewport', label_xy, formatString(label), angle, align);
  1337. }
  1338. q = n.Next();
  1339. }
  1340. }
  1341. //--------------------------------------------------------------------------------------------------
  1342. // http://stackoverflow.com/questions/3231459/create-unique-id-with-javascript
  1343. function uniqueid(){
  1344. // always start with a letter (for DOM friendlyness)
  1345. var idstr=String.fromCharCode(Math.floor((Math.random()*25)+65));
  1346. do {
  1347. // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
  1348. var ascicode=Math.floor((Math.random()*42)+48);
  1349. if (ascicode<58 || ascicode>64){
  1350. // exclude all chars between : (58) and @ (64)
  1351. idstr+=String.fromCharCode(ascicode);
  1352. }
  1353. } while (idstr.length<32);
  1354. return (idstr);
  1355. }
  1356. //--------------------------------------------------------------------------------------------------
  1357. function draw_tree_labels(nexus, t, drawing_type) {
  1358. // label leaves...
  1359. var n = new NodeIterator(t.root);
  1360. var q = n.Begin();
  1361. while (q != null)
  1362. {
  1363. if (q.IsLeaf())
  1364. {
  1365. var label = q.label;
  1366. if (nexus.treesblock.translate)
  1367. {
  1368. if (nexus.treesblock.translate[label])
  1369. {
  1370. label = nexus.treesblock.translate[label];
  1371. }
  1372. }
  1373. switch (drawing_type)
  1374. {
  1375. case 'radial':
  1376. var align = 'left';
  1377. var angle = q.angle * 180.0/Math.PI;
  1378. if ((q.angle < -Math.PI/2) || (q.angle > Math.PI/2))
  1379. {
  1380. align = 'right';
  1381. angle += 180.0;
  1382. }
  1383. drawRotatedText('viewport', q.xy, label, angle, align)
  1384. break;
  1385. case 'circle':
  1386. case 'circlephylogram':
  1387. var align = 'left';
  1388. var angle = q.angle * 180.0/Math.PI;
  1389. if ((q.angle > Math.PI/2.0) && (q.angle < 1.5 * Math.PI))
  1390. {
  1391. align = 'right';
  1392. angle += 180.0;
  1393. }
  1394. var r = td.root_length + td.settings.width/2;
  1395. var pt = [];
  1396. pt['x'] = Math.cos(q.angle) * r;
  1397. pt['y'] = Math.sin(q.angle) * r;
  1398. drawRotatedText('viewport', pt, label, angle, align);
  1399. //drawRotatedText('viewport', q.xy, label, angle, align);
  1400. break;
  1401. case 'cladogram':
  1402. case 'rectanglecladogram':
  1403. case 'phylogram':
  1404. default:
  1405. drawText('viewport', q.xy, label);
  1406. break;
  1407. }
  1408. }
  1409. q = n.Next();
  1410. }
  1411. }
  1412. //--------------------------------------------------------------------------------------------------
  1413. /**
  1414. *
  1415. * Base64 encode / decode
  1416. * http://www.webtoolkit.info/
  1417. *
  1418. **/
  1419. var Base64 = {
  1420. // private property
  1421. _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  1422. // public method for encoding
  1423. encode : function (input) {
  1424. var output = "";
  1425. var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  1426. var i = 0;
  1427. input = Base64._utf8_encode(input);
  1428. while (i < input.length) {
  1429. chr1 = input.charCodeAt(i++);
  1430. chr2 = input.charCodeAt(i++);
  1431. chr3 = input.charCodeAt(i++);
  1432. enc1 = chr1 >> 2;
  1433. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  1434. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  1435. enc4 = chr3 & 63;
  1436. if (isNaN(chr2)) {
  1437. enc3 = enc4 = 64;
  1438. } else if (isNaN(chr3)) {
  1439. enc4 = 64;
  1440. }
  1441. output = output +
  1442. this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
  1443. this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
  1444. }
  1445. return output;
  1446. },
  1447. // public method for decoding
  1448. decode : function (input) {
  1449. var output = "";
  1450. var chr1, chr2, chr3;
  1451. var enc1, enc2, enc3, enc4;
  1452. var i = 0;
  1453. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  1454. while (i < input.length) {
  1455. enc1 = this._keyStr.indexOf(input.charAt(i++));
  1456. enc2 = this._keyStr.indexOf(input.charAt(i++));
  1457. enc3 = this._keyStr.indexOf(input.charAt(i++));
  1458. enc4 = this._keyStr.indexOf(input.charAt(i++));
  1459. chr1 = (enc1 << 2) | (enc2 >> 4);
  1460. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  1461. chr3 = ((enc3 & 3) << 6) | enc4;
  1462. output = output + String.fromCharCode(chr1);
  1463. if (enc3 != 64) {
  1464. output = output + String.fromCharCode(chr2);
  1465. }
  1466. if (enc4 != 64) {
  1467. output = output + String.fromCharCode(chr3);
  1468. }
  1469. }
  1470. output = Base64._utf8_decode(output);
  1471. return output;
  1472. },
  1473. // private method for UTF-8 encoding
  1474. _utf8_encode : function (string) {
  1475. string = string.replace(/\r\n/g,"\n");
  1476. var utftext = "";
  1477. for (var n = 0; n < string.length; n++) {
  1478. var c = string.charCodeAt(n);
  1479. if (c < 128) {
  1480. utftext += String.fromCharCode(c);
  1481. }
  1482. else if((c > 127) && (c < 2048)) {
  1483. utftext += String.fromCharCode((c >> 6) | 192);
  1484. utftext += String.fromCharCode((c & 63) | 128);
  1485. }
  1486. else {
  1487. utftext += String.fromCharCode((c >> 12) | 224);
  1488. utftext += String.fromCharCode(((c >> 6) & 63) | 128);
  1489. utftext += String.fromCharCode((c & 63) | 128);
  1490. }
  1491. }
  1492. return utftext;
  1493. },
  1494. // private method for UTF-8 decoding
  1495. _utf8_decode : function (utftext) {
  1496. var string = "";
  1497. var i = 0;
  1498. var c = c1 = c2 = 0;
  1499. while ( i < utftext.length ) {
  1500. c = utftext.charCodeAt(i);
  1501. if (c < 128) {
  1502. string += String.fromCharCode(c);
  1503. i++;
  1504. }
  1505. else if((c > 191) && (c < 224)) {
  1506. c2 = utftext.charCodeAt(i+1);
  1507. string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  1508. i += 2;
  1509. }
  1510. else {
  1511. c2 = utftext.charCodeAt(i+1);
  1512. c3 = utftext.charCodeAt(i+2);
  1513. string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  1514. i += 3;
  1515. }
  1516. }
  1517. return string;
  1518. }
  1519. }
  1520. // http://stackoverflow.com/questions/498970/how-do-i-trim-a-string-in-javascript
  1521. if (!String.prototype.trim)
  1522. {
  1523. String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');};
  1524. }
  1525. function draw_tree(element_id, drawing_type)
  1526. {
  1527. var loading = document.getElementById('loading');
  1528. loading.style.visibility = 'shown';
  1529. var t = new Tree();
  1530. var element = document.getElementById(element_id);
  1531. var newick = element.value;
  1532. newick = newick.trim(newick);
  1533. document.getElementById('message').innerHTML='Parsing...';
  1534. t.Parse(newick);
  1535. if (t.error != 0)
  1536. {
  1537. document.getElementById('message').innerHTML='Error parsing tree';
  1538. throw "Error parsing tree: " + errors[t.error-1];
  1539. }
  1540. else
  1541. {
  1542. document.getElementById('message').innerHTML='';
  1543. t.ComputeWeights(t.root);
  1544. var td = null;
  1545. var selectmenu = document.getElementById('style');
  1546. switch (drawing_type)
  1547. {
  1548. case 'rectanglecladogram':
  1549. td = new RectangleTreeDrawer();
  1550. break;
  1551. case 'phylogram':
  1552. if (t.has_edge_lengths)
  1553. {
  1554. td = new PhylogramTreeDrawer();
  1555. }
  1556. else
  1557. {
  1558. td = new RectangleTreeDrawer();
  1559. }
  1560. break;
  1561. case 'circle':
  1562. td = new CircleTreeDrawer();
  1563. break;
  1564. case 'circlephylogram':
  1565. if (t.has_edge_lengths)
  1566. {
  1567. td = new CirclePhylogramDrawer();
  1568. }
  1569. else
  1570. {
  1571. td = new CircleTreeDrawer();
  1572. }
  1573. break;
  1574. case 'radial':
  1575. td = new RadialTreeDrawer();
  1576. break
  1577. case 'cladogram':
  1578. default:
  1579. td = new TreeDrawer();
  1580. break;
  1581. }
  1582. // clear existing diagram, if any
  1583. var svg = document.getElementById('svg');
  1584. while (svg.hasChildNodes())
  1585. {
  1586. svg.removeChild(svg.lastChild);
  1587. }
  1588. var g = document.createElementNS('http://www.w3.org/2000/svg','g');
  1589. g.setAttribute('id','viewport');
  1590. svg.appendChild(g);
  1591. td.Init(t, {svg_id: 'viewport', width:VIEWER_WIDTH, height:VIEWER_HEIGHT, fontHeight:10, root_length:0.1} );
  1592. td.CalcCoordinates();
  1593. td.Draw();
  1594. // font size
  1595. var cssStyle = document.createElementNS('http://www.w3.org/2000/svg','style');
  1596. cssStyle.setAttribute('type','text/css');
  1597. var font_size = Math.floor(td.settings.height/t.num_leaves);
  1598. font_size = Math.max(font_size, 1);
  1599. var style=document.createTextNode("text{font-size:" + font_size + "px;}");
  1600. cssStyle.appendChild(style);
  1601. svg.appendChild(cssStyle);
  1602. // label leaves...
  1603. var n = new NodeIterator(t.root);
  1604. var q = n.Begin();
  1605. while (q != null)
  1606. {
  1607. if (q.IsLeaf())
  1608. {
  1609. switch (drawing_type)
  1610. {
  1611. case 'circle':
  1612. case 'circlephylogram':
  1613. var align = 'left';
  1614. var angle = q.angle * 180.0/Math.PI;
  1615. if ((q.angle > Math.PI/2.0) && (q.angle < 1.5 * Math.PI))
  1616. {
  1617. align = 'right';
  1618. angle += 180.0;
  1619. }
  1620. drawRotatedText('viewport', q.xy, q.label, angle, align)
  1621. break;
  1622. case 'cladogram':
  1623. case 'rectanglecladogram':
  1624. case 'phylogram':
  1625. default:
  1626. drawText('viewport', q.xy, q.label);
  1627. break;
  1628. }
  1629. }
  1630. q = n.Next();
  1631. }
  1632. // Scale to fit window
  1633. var bbox = svg.getBBox();
  1634. var scale = Math.min(td.settings.width/bbox.width, td.settings.height/bbox.height) * 0.75;
  1635. // move drawing to centre of viewport
  1636. var viewport = document.getElementById('viewport');
  1637. baseMatrix = [1*scale, 0, 0, 1*scale, 100, 100];
  1638. setMatrix(viewport, baseMatrix);
  1639. // centre
  1640. bbox = svg.getBBox();
  1641. if (bbox.x < 0)
  1642. {
  1643. pan(viewport, bbox.width/2, bbox.height/2);
  1644. } else {
  1645. pan(viewport, -25, 25);
  1646. }
  1647. // pan
  1648. $('svg').svgPan('viewport');
  1649. }
  1650. loading.style.visibility = 'hidden';
  1651. // Make SVG downloadable
  1652. // http://stackoverflow.com/questions/8379923/save-svg-image-rendered-by-a-javascript-to-local-disk-as-png-file/8861315#8861315
  1653. // http://stackoverflow.com/a/4228053/9684
  1654. // http://stackoverflow.com/questions/2483919/how-to-save-svg-canvas-to-local-filesystem#comment23679242_4228053
  1655. var svgString = new XMLSerializer().serializeToString(svg);
  1656. var b64 = Base64.encode(svgString);
  1657. $("#download").attr('href', "data:image/svg+xml;base64,\n" + b64);
  1658. }
  1659. function smooth_scale(x) {
  1660. return (Math.pow(Math.abs(x),2) * (3-2*Math.abs(x)))
  1661. }
  1662. function getMatrix(viewport) {
  1663. return viewport.getAttribute('transform').split('(')[1].split(')')[0].split(',').map(parseFloat);
  1664. }
  1665. function setMatrix(viewport, matrix) {
  1666. viewport.setAttribute('transform', 'matrix(' + matrix.join(',') + ')');
  1667. }
  1668. function zoom(viewport, scale) {
  1669. matrix = getMatrix(viewport);
  1670. old_scale = matrix[0];
  1671. if (scale * old_scale < 0.1) scale = 0.1/old_scale;
  1672. if (scale * old_scale > 100) scale = 100/old_scale;
  1673. for (var i = 0; i < matrix.length; i++)
  1674. {
  1675. matrix[i] *= scale;
  1676. }
  1677. bbox = viewport.getBBox();
  1678. matrix[4] += (1-scale) * (bbox.width-50)/2;
  1679. matrix[5] += (1-scale) * bbox.height/2;
  1680. setMatrix(viewport, matrix);
  1681. }
  1682. function pan(viewport, dx, dy) {
  1683. matrix = getMatrix(viewport);
  1684. matrix[4] += dx;
  1685. matrix[5] += dy;
  1686. setMatrix(viewport, matrix);
  1687. }
  1688. function zoom_in() {
  1689. var viewport = document.getElementById('viewport');
  1690. gradual_zoom(viewport, ZOOM_IN);
  1691. }
  1692. function zoom_out() {
  1693. var viewport = document.getElementById('viewport');
  1694. gradual_zoom(viewport, ZOOM_OUT);
  1695. }
  1696. function mouse_zoom_in(event) {
  1697. pan_to_mouse(event, ZOOM_IN);
  1698. zoom_in();
  1699. }
  1700. function mouse_zoom_out(event) {
  1701. pan_to_mouse(event, ZOOM_OUT);
  1702. zoom_out();
  1703. }
  1704. function pan_btn(dir) {
  1705. start_pan();
  1706. var viewport = document.getElementById('viewport');
  1707. matrix = getMatrix(viewport);
  1708. scale = matrix[0];
  1709. d = 100;
  1710. dx = dir == 'l' ? d : (dir == 'r' ? -d : 0);
  1711. dy = dir == 'u' ? d : (dir == 'd' ? -d : 0);
  1712. gradual_pan(viewport, dx, dy);
  1713. }
  1714. function pan_to_mouse(e, scale) {
  1715. var container = document.getElementById('treeContainer');
  1716. var viewport = document.getElementById('viewport');
  1717. offset = $(container).offset();
  1718. dx = e.pageX - (offset.left + container.offsetWidth/2);
  1719. dy = e.pageY - (offset.top + container.offsetHeight/2);
  1720. gradual_pan(viewport, dx*(1-scale), dy*(1-scale));
  1721. }
  1722. var panning = 0;
  1723. var pan_pressed = false;
  1724. function start_pan() { pan_pressed = true; }
  1725. function stop_pan() { pan_pressed = false; }
  1726. function gradual_pan(viewport, dx, dy) {
  1727. if (panning) return;
  1728. steps = 25;
  1729. duration = 0.5;
  1730. panning = 0;
  1731. last_time = new Date()/1000;
  1732. function frame() {
  1733. this_time = new Date()/1000;
  1734. time_elapsed = this_time - last_time;
  1735. last_time = this_time;
  1736. panning += time_elapsed/duration;
  1737. if (panning >= 1) panning = 1;
  1738. d = smooth_scale(panning);
  1739. pan(viewport, d*dx/steps, d*dy/steps)
  1740. if (panning >= 1 && !(pan_pressed)) {
  1741. panning = 0;
  1742. clearInterval(id);
  1743. }
  1744. }
  1745. var id = setInterval(frame, 1000*duration/steps);
  1746. }
  1747. var zooming = 0;
  1748. var zoom_pressed = false;
  1749. function start_zoom() { zoom_pressed = true; }
  1750. function stop_zoom() { zoom_pressed = false; }
  1751. function gradual_zoom(viewport, scale) {
  1752. if (zooming > 0 || panning || pan_pressed) return;
  1753. var viewport = document.getElementById('viewport');
  1754. matrix = getMatrix(viewport);
  1755. initial_scale = matrix[0];
  1756. new_scale = initial_scale * scale;
  1757. old_scale = initial_scale;
  1758. steps = 25;
  1759. duration = 0.5;
  1760. last_time = new Date()/1000;
  1761. function frame() {
  1762. console.log(zoom_pressed);
  1763. this_time = new Date()/1000;
  1764. time_elapsed = this_time - last_time;
  1765. last_time = this_time;
  1766. mult = scale < 1 ? zooming : Math.pow(zooming, 2.5);
  1767. this_scale = initial_scale + (new_scale-initial_scale) * mult;
  1768. this_step = this_scale / old_scale;

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