phylogician-ts
Version:
Module to read, manipulate and write phylogenetic trees. Written in TypeScript
168 lines • 6.57 kB
JavaScript
;
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