Guest User

Untitled

a guest
Jan 21st, 2018
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.25 KB | None | 0 0
  1. /*
  2. d3.phylogram.js
  3. Wrapper around a d3-based phylogram (tree where branch lengths are scaled)
  4. Also includes a radial dendrogram visualization (branch lengths not scaled)
  5. along with some helper methods for building angled-branch trees.
  6.  
  7. d3.phylogram.build(selector, nodes, options)
  8. Creates a phylogram.
  9. Arguments:
  10. selector: selector of an element that will contain the SVG
  11. nodes: JS object of nodes
  12. Options:
  13. width
  14. Width of the vis, will attempt to set a default based on the width of
  15. the container.
  16. height
  17. Height of the vis, will attempt to set a default based on the height
  18. of the container.
  19. vis
  20. Pre-constructed d3 vis.
  21. tree
  22. Pre-constructed d3 tree layout.
  23. children
  24. Function for retrieving an array of children given a node. Default is
  25. to assume each node has an attribute called "branchset"
  26. diagonal
  27. Function that creates the d attribute for an svg:path. Defaults to a
  28. right-angle diagonal.
  29. skipTicks
  30. Skip the tick rule.
  31. skipBranchLengthScaling
  32. Make a dendrogram instead of a phylogram.
  33. skipTreeNodeStyle
  34. Don't draw circles at the root and leaf nodes
  35. skipInteriorBranchLengths
  36. Don't draw the interior branch lengths
  37. skipLabels
  38. Don't add labels at leaf nodes
  39. strokeWidth
  40. Size for stroke that draws the tree (e.g. "1px")
  41. strokeColor
  42. Color for stroke (e.g. "#dedede")
  43.  
  44. d3.phylogram.buildRadial(selector, nodes, options)
  45. Creates a radial dendrogram.
  46. Options: same as build, but without diagonal, skipTicks, and
  47. skipBranchLengthScaling
  48.  
  49. d3.phylogram.rightAngleDiagonal()
  50. Similar to d3.diagonal except it create an orthogonal crook instead of a
  51. smooth Bezier curve.
  52.  
  53. d3.phylogram.radialRightAngleDiagonal()
  54. d3.phylogram.rightAngleDiagonal for radial layouts.
  55. */
  56.  
  57. if (!d3) { throw "d3 wasn't included!"};
  58. (function() {
  59. d3.phylogram = {}
  60. d3.phylogram.rightAngleDiagonal = function() {
  61. var projection = function(d) { return [d.y, d.x]; }
  62.  
  63. var path = function(pathData) {
  64. return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
  65. }
  66.  
  67. function diagonal(diagonalPath, i) {
  68. var source = diagonalPath.source,
  69. target = diagonalPath.target,
  70. midpointX = (source.x + target.x) / 2,
  71. midpointY = (source.y + target.y) / 2,
  72. pathData = [source, {x: target.x, y: source.y}, target];
  73. pathData = pathData.map(projection);
  74. return path(pathData)
  75. }
  76.  
  77. diagonal.projection = function(x) {
  78. if (!arguments.length) return projection;
  79. projection = x;
  80. return diagonal;
  81. };
  82.  
  83. diagonal.path = function(x) {
  84. if (!arguments.length) return path;
  85. path = x;
  86. return diagonal;
  87. };
  88.  
  89. return diagonal;
  90. }
  91.  
  92. d3.phylogram.radialRightAngleDiagonal = function() {
  93. return d3.phylogram.rightAngleDiagonal()
  94. .path(function(pathData) {
  95. var src = pathData[0],
  96. mid = pathData[1],
  97. dst = pathData[2],
  98. radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
  99. srcAngle = d3.phylogram.coordinateToAngle(src, radius),
  100. midAngle = d3.phylogram.coordinateToAngle(mid, radius),
  101. clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
  102. rotation = 0,
  103. largeArc = 0,
  104. sweep = clockwise ? 0 : 1;
  105. return 'M' + src + ' ' +
  106. "A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
  107. 'L' + dst;
  108. })
  109. .projection(function(d) {
  110. var r = d.y, a = (d.x - 90) / 180 * Math.PI;
  111. return [r * Math.cos(a), r * Math.sin(a)];
  112. })
  113. }
  114.  
  115. // Convert XY and radius to angle of a circle centered at 0,0
  116. d3.phylogram.coordinateToAngle = function(coord, radius) {
  117. var wholeAngle = 2 * Math.PI,
  118. quarterAngle = wholeAngle / 4
  119.  
  120. var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
  121. coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
  122.  
  123. // Since this is just based on the angle of the right triangle formed
  124. // by the coordinate and the origin, each quad will have different
  125. // offsets
  126. switch (coordQuad) {
  127. case 1:
  128. coordAngle = quarterAngle - coordBaseAngle
  129. break
  130. case 2:
  131. coordAngle = quarterAngle + coordBaseAngle
  132. break
  133. case 3:
  134. coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
  135. break
  136. case 4:
  137. coordAngle = 3*quarterAngle + coordBaseAngle
  138. }
  139. return coordAngle
  140. }
  141.  
  142. d3.phylogram.styleTreeNodes = function(vis) {
  143. vis.selectAll('g.leaf.node')
  144. .append("svg:circle")
  145. .attr("r", 4.5)
  146. .attr('stroke', 'yellowGreen')
  147. .attr('fill', 'greenYellow')
  148. .attr('stroke-width', '2px');
  149.  
  150. vis.selectAll('g.root.node')
  151. .append('svg:circle')
  152. .attr("r", 4.5)
  153. .attr('fill', 'steelblue')
  154. .attr('stroke', '#369')
  155. .attr('stroke-width', '2px');
  156. }
  157.  
  158. function scaleBranchLengths(nodes, w) {
  159. // Visit all nodes and adjust y pos width distance metric
  160. var visitPreOrder = function(root, callback) {
  161. callback(root)
  162. if (root.children) {
  163. for (var i = root.children.length - 1; i >= 0; i--){
  164. visitPreOrder(root.children[i], callback)
  165. };
  166. }
  167. }
  168. visitPreOrder(nodes[0], function(node) {
  169. node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.data.length || 0)
  170. })
  171. var rootDists = nodes.map(function(n) { return n.rootDist; });
  172. var yscale = d3.scale.linear()
  173. .domain([0, d3.max(rootDists)])
  174. .range([0, w]);
  175. visitPreOrder(nodes[0], function(node) {
  176. node.y = yscale(node.rootDist)
  177. })
  178. return yscale
  179. }
  180.  
  181.  
  182. d3.phylogram.build = function(selector, nodes, options) {
  183. options = options || {}
  184. var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
  185. h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
  186. w = parseInt(w),
  187. h = parseInt(h);
  188. var tree = options.tree || d3.layout.cluster()
  189. .size([h, w])
  190. .sort(function(node) { return node.children ? node.children.length : -1; })
  191. .children(options.children || function(node) {
  192. return node.branchset
  193. });
  194. var diagonal = options.diagonal || d3.phylogram.rightAngleDiagonal();
  195. var vis = options.vis || d3.select(selector).append("svg:svg")
  196. .attr("width", w + 300)
  197. .attr("height", h + 30)
  198. .append("svg:g")
  199. .attr("transform", "translate(20, 20)");
  200. var nodes = tree(nodes);
  201.  
  202. var strokeWidth = options.strokeWidth || "4px";
  203. var strokeColor = options.strokeColor || "#aaa";
  204.  
  205. if (options.skipBranchLengthScaling) {
  206. var yscale = d3.scale.linear()
  207. .domain([0, w])
  208. .range([0, w]);
  209. } else {
  210. var yscale = scaleBranchLengths(nodes, w)
  211. }
  212.  
  213. if (!options.skipTicks) {
  214. vis.selectAll('line')
  215. .data(yscale.ticks(10))
  216. .enter().append('svg:line')
  217. .attr('y1', 0)
  218. .attr('y2', h)
  219. .attr('x1', yscale)
  220. .attr('x2', yscale)
  221. .attr("stroke", "#ddd");
  222.  
  223. vis.selectAll("text.rule")
  224. .data(yscale.ticks(10))
  225. .enter().append("svg:text")
  226. .attr("class", "rule")
  227. .attr("x", yscale)
  228. .attr("y", 0)
  229. .attr("dy", -3)
  230. .attr("text-anchor", "middle")
  231. .attr('font-size', '8px')
  232. .attr('fill', '#ccc')
  233. .text(function(d) { return Math.round(d*100) / 100; });
  234. }
  235.  
  236. var link = vis.selectAll("path.link")
  237. .data(tree.links(nodes))
  238. .enter().append("svg:path")
  239. .attr("class", "link")
  240. .attr("d", diagonal)
  241. .attr("fill", "none")
  242. .attr("stroke", strokeColor)
  243. .attr("stroke-width", strokeWidth);
  244.  
  245. var node = vis.selectAll("g.node")
  246. .data(nodes)
  247. .enter().append("svg:g")
  248. .attr("class", function(n) {
  249. if (n.children) {
  250. if (n.depth == 0) {
  251. return "root node"
  252. } else {
  253. return "inner node"
  254. }
  255. } else {
  256. return "leaf node"
  257. }
  258. })
  259. .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
  260.  
  261. if (!options.skipTreeNodeStyle) {
  262. d3.phylogram.styleTreeNodes(vis)
  263. }
  264.  
  265. if (!options.skipInteriorBranchLengths) {
  266. vis.selectAll('g.inner.node')
  267. .append("svg:text")
  268. .attr("dx", -6)
  269. .attr("dy", -6)
  270. .attr("text-anchor", 'end')
  271. .attr('font-size', '8px')
  272. .attr('fill', '#ccc')
  273. .text(function(d) { return d.data.length; });
  274. }
  275.  
  276. if (!options.skipLabels) {
  277. vis.selectAll('g.leaf.node').append("svg:text")
  278. .attr("dx", 8)
  279. .attr("dy", 3)
  280. .attr("text-anchor", "start")
  281. .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
  282. .attr('font-size', '10px')
  283. .attr('fill', 'black')
  284. .text(function(d) { return d.data.name + ' ('+d.data.length+')'; });
  285. }
  286.  
  287. return {tree: tree, vis: vis}
  288. }
  289.  
  290. d3.phylogram.buildRadial = function(selector, nodes, options) {
  291. options = options || {}
  292. var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
  293. r = w / 2,
  294. labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
  295.  
  296. var vis = d3.select(selector).append("svg:svg")
  297. .attr("width", r * 2)
  298. .attr("height", r * 2)
  299. .append("svg:g")
  300. .attr("transform", "translate(" + r + "," + r + ")");
  301.  
  302. var tree = d3.layout.tree()
  303. .size([360, r - labelWidth])
  304. .sort(function(node) { return node.children ? node.children.length : -1; })
  305. .children(options.children || function(node) {
  306. return node.branchset
  307. })
  308. .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
  309.  
  310. var phylogram = d3.phylogram.build(selector, nodes, {
  311. vis: vis,
  312. tree: tree,
  313. skipBranchLengthScaling: true,
  314. skipTicks: true,
  315. skipLabels: options.skipLabels,
  316. diagonal: d3.phylogram.radialRightAngleDiagonal()
  317. })
  318. vis.selectAll('g.node')
  319. .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
  320.  
  321. if (!options.skipLabels) {
  322. vis.selectAll('g.leaf.node text')
  323. .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
  324. .attr("dy", ".31em")
  325. .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
  326. .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
  327. .attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
  328. .attr('font-size', '10px')
  329. .attr('fill', 'black')
  330. .text(function(d) { return d.data.name; });
  331.  
  332. vis.selectAll('g.inner.node text')
  333. .attr("dx", function(d) { return d.x < 180 ? -6 : 6; })
  334. .attr("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
  335. .attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; });
  336. }
  337.  
  338. return {tree: tree, vis: vis}
  339. }
  340. }());
Add Comment
Please, Sign In to add comment