UNPKG

phylogician-ts

Version:

Module to read, manipulate and write phylogenetic trees. Written in TypeScript

168 lines 6.57 kB
"use strict"; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const d3 = __importStar(require("d3")); const TreeLayout_1 = require("./TreeLayout"); const loglevel_colored_prefix_1 = require("loglevel-colored-prefix"); class Circular extends TreeLayout_1.TreeLayout { constructor(tree, svgParams, loglevel = 'info') { super(tree, svgParams); this.logger = new loglevel_colored_prefix_1.Logger(loglevel); } /** * Calculates all parameters of the tree * * @param {boolean} scaling * @returns {this} * @memberof Circular */ calcTree(scaling) { const log = this.logger.getLogger('CircularTreeLayout::calcTree'); super.calcLabelDims(); const center = { x: this.svgParams.width / 2, y: this.svgParams.height / 2, }; this.calcAngle() .calcRadius(scaling) .calcBranchPath(); this.tree.nodes.forEach(node => { const radius = node.getPosR(); const angle = node.getPosA(); const x = center.x + radius * Math.cos(angle); const y = center.y + radius * Math.sin(angle); const nodeId = node.id; if (nodeId !== undefined) { log.debug(`Node: ${nodeId}, x: ${x}, y: ${y} `); node.setPosX(x); node.setPosY(y); } }); return this; } /** * Calculates the branch path * * @returns {this} * @memberof Circular */ calcBranchPath() { const center = { x: this.svgParams.width / 2, y: this.svgParams.height / 2, }; this.tree.nodes.forEach(node => { const parentId = node.getParentNodeId(); if (parentId !== null) { const parent = this.tree.getNode(parentId); const startRadius = parent.getPosR(); const endRadius = node.getPosR(); const startAngle = parent.getPosA(); const endAngle = node.getPosA(); const c0 = Math.cos(startAngle); const s0 = Math.sin(startAngle); const c1 = Math.cos(endAngle); const s1 = Math.sin(endAngle); let arc = ''; if (startAngle !== endAngle) { arc = `A${startRadius} ${startRadius} 0 0 ${endAngle > startAngle ? 1 : 0} ${center.x + startRadius * c1} ${center.y + startRadius * s1}`; } const branch = `M${center.x + startRadius * c0},${center.y + startRadius * s0}${arc}L${center.x + endRadius * c1},${center.y + endRadius * s1}`; node.setBranchPath(branch); } }); return this; } /** * Helper function to place label by the corresponding node * * @param {number} id * @returns {string} * @memberof Circular */ labelPlace(id) { const center = { x: this.svgParams.width / 2, y: this.svgParams.height / 2, }; const node = this.tree.getNode(id); const angle = (node.getPosA() * 180) / Math.PI; return `rotate(${angle < 90 ? angle : angle < 270 ? angle + 180 : angle}, ${node.getPosX()}, ${node.getPosY()}) translate(${angle < 90 ? 10 : angle < 270 ? -10 : 10})`; } /** * Helper function to determine the anchor of text * * @param {number} id * @returns {string} * @memberof Circular */ labelAnchor(id) { const node = this.tree.getNode(id); const angle = (node.getPosA() * 180) / Math.PI; return angle < 90 ? 'start' : angle < 270 ? 'end' : 'start'; } calcAngle() { const log = this.logger.getLogger('CircularTreeLayout::calcAngle'); const leaves = this.tree.getAllLeafIds(); const yDom = d3 .scaleLinear() .domain([0, this.tree.getNumberOfLeafs()]) .range([0, 2 * Math.PI]); leaves.forEach((leaf, i) => this.tree.getNode(leaf).setPosA(yDom(i))); const internalIds = this.tree.getInternalIds().reverse(); internalIds.forEach(internalId => { const internal = this.tree.getNode(internalId); const childrenPos = []; internal.children.forEach(child => { childrenPos.push(this.tree.getNode(child).getPosA()); }); const posA = childrenPos.reduce((sum, pos) => sum + pos) / childrenPos.length; log.debug(`Node: ${internalId}, angle: ${posA} :: ${(posA / 180) * Math.PI} `); internal.setPosA(posA); }); return this; } calcRadius(scaling = false) { const log = this.logger.getLogger('CircularTreeLayout::calcRadius'); const longestLabel = this.getLongestLabel(); if (scaling) { const xDom = d3 .scaleLinear() .domain([0, this.tree.getMaxDistanceToRoot()]) .range([0, this.svgParams.width / 2 - this.svgParams.margin - longestLabel]); this.tree.nodes.forEach(node => { const id = node.id; const posR = xDom(this.tree.getDistanceToRoot(id)); log.debug(`Node: ${id}, r: ${posR} `); node.setPosR(posR); }); } else { const maxHops = this.tree.getMaxHopsToRoot(); log.debug(`Scale: ${scaling}, MaxHops: ${maxHops}`); const xDom = d3 .scaleLinear() .domain([maxHops, 0]) .range([0, this.svgParams.width / 2 - this.svgParams.margin - longestLabel]); const nodeListCounter = this.tree.getAllMaxHopsToChildLeafs(); this.tree.nodes.forEach(node => { const nodeId = node.id; if (nodeId !== undefined) { log.debug(`Node: ${nodeId}, MaxHopsToChildLeafs: ${nodeListCounter[nodeId]}, xDom: ${xDom(nodeListCounter[nodeId])} `); node.setPosR(xDom(nodeListCounter[nodeId])); } }); } return this; } } exports.Circular = Circular; //# sourceMappingURL=CircularTreeLayout.js.map