Commit 579566ca authored by Remi  PLANEL's avatar Remi PLANEL

Make phylogram compute position depending also with the node's radius and the link stroke width

parent 6ca0d610
import { select, Selection } from "d3-selection";
import { HierarchyPointLink } from "d3-hierarchy";
import { HierarchyPointLink, HierarchyLink } from "d3-hierarchy";
import { PhyloTreeNode } from "../../../types";
......@@ -30,14 +30,13 @@ export default function () {
const linksU = links.merge(linksE);
linksU
.attr("d", ({ source, target }) => {
return "M" + target.y + " " + target.x
+ " H" + source.y
+ " V" + source.x;
})
.attr("fill", "none")
.attr("stroke-width", 2)
.attr("stroke", "black");
.attr("d", ({ source, target }) => {
return "M" + target.y + " " + target.x
+ " H" + source.y
+ " V" + source.x;
})
.attr("stroke-width", d => d.source.data.link.strokeWidth)
.attr("stroke", d => d.source.data.link.strokeColor);
});
}
......
......@@ -7,8 +7,7 @@ export default function () {
function genericNode(
_selection: Selection<SVGGElement, HierarchyPointNode<PhyloTreeNode>[], any, any>
) {
const defaultR = 4;
const defaultFill = "#000";
const classes = {
node: "generic-node",
label: "label"
......@@ -27,22 +26,15 @@ export default function () {
// circle
nodeE.append<SVGCircleElement>("circle")
.attr("r",
({ data: { nodes } }) => (nodes && nodes.r) ? nodes.r : defaultR
)
.attr("fill",
({ data: { nodes } }) => (nodes && nodes.fill) ? nodes.fill : defaultFill
)
.attr(
"stroke-width",
({ data: { nodes } }) => (nodes && nodes.strokeWidth) ? nodes.strokeWidth : 10
)
.attr("r", d => d.data.node.r)
.attr("fill", d => d.data.node.fill)
.attr("stroke-width", d => d.data.node.strokeWidth)
// text
nodeE.append<SVGTextElement>("text")
.classed(classes.label, true)
.attr("dy", "0.31em")
.attr("font-family","monospace")
.attr("x", ({ data: { nodes } }) => (nodes && nodes.r) ? (nodes.r) + 2 : defaultR + 2);
.attr("font-family", "monospace")
.attr("x", d => d.data.node.r);
const nodeU = node.merge(nodeE)
.attr("transform", d => `translate(${d.y}, ${d.x})`)
......@@ -69,5 +61,6 @@ export default function () {
});
}
return genericNode
}
\ No newline at end of file
......@@ -35,6 +35,14 @@ export default function () {
// UPDATE
const phylogramU = phylogram.merge(phylogramE);
// translate depending on the root node size
// phylogramU.attr("transform", ({ data: { node: nodes, link: links } }) => {
// let marginLeft = 0;
// marginLeft += (nodes && nodes.r !== undefined) ? nodes.r : nodeComponent.getDefautR();
// marginLeft += (links && links.strokeWidth !== undefined) ? links.strokeWidth / 2 : linkComponent.getDefaultStrokeWidth() / 2;
// return `translate(${marginLeft},0)`;
// })
// Nodes
phylogramU.select<SVGGElement>('.' + classes.nodes)
.datum(d => d.descendants().reverse())
......
......@@ -5,10 +5,10 @@ export default function () {
let dx = 1;
let dy = 1;
let nodeSize: boolean | null = null;
let separation = defaultSeparation;
let separation: (a: d3.HierarchyPointNode<RawPhyloTreeNode>, b:d3.HierarchyPointNode<RawPhyloTreeNode>) => number = defaultSeparation;
function cladogram(data: RawPhyloTreeNode) {
// Apply the layout hierarchy.
cluster()
cluster<RawPhyloTreeNode>()
.separation(separation)
(hierarchy(data))
return data;
......@@ -28,8 +28,8 @@ export default function () {
return cladogram;
}
cladogram.separation = function <T>(
separationCb: (a: d3.HierarchyNode<T>, b:d3.HierarchyNode<T>) => number
cladogram.separation = function(
separationCb: (a: d3.HierarchyPointNode<RawPhyloTreeNode>, b:d3.HierarchyPointNode<RawPhyloTreeNode>) => number
) {
separation = separationCb;
}
......
import { hierarchy } from "d3-hierarchy";
import { RawPhyloTreeNode, PhyloTreeNode } from "../types";
import { defaultSeparation } from "./phylotree"
import { RawPhyloTreeNode, PhyloTreeNode, PartialNodeInfo, PartialLinkInfo } from "../types";
import { defaultSeparation, defaultNodeR, defaultNodeFill, defaultNodeStrokeWidth, defaultLinkColor, defaultLinkWidth } from "./phylotree"
export default function () {
let dx = 1;
let dy = 1;
let nodeSize: boolean | null = null;
let separation = defaultSeparation;
let separation: (a: d3.HierarchyPointNode<PhyloTreeNode>, b: d3.HierarchyPointNode<PhyloTreeNode>) => number = defaultSeparation;
function phylogram(data: RawPhyloTreeNode) {
// Apply the layout hierarchy.
const root = hierarchy(data);
// Compute the lengthFromRoot
const { maxLengthFromRoot } = computeLengthFromRoot(root);
const maxLengthFromRoot = computeLengthFromRoot(root);
const { pointPhylotreeRoot, leftNode, rightNode, deepestNodeWithLabel } =
setRelativePosition(root, maxLengthFromRoot);
return setAbsolutePosition(
......@@ -26,24 +27,32 @@ export default function () {
function computeLengthFromRoot(root: d3.HierarchyNode<RawPhyloTreeNode>) {
let maxLengthFromRoot = 0;
let maxLabel = 0;
const phyloTreeNode = root as d3.HierarchyNode<PhyloTreeNode>;
phyloTreeNode.eachBefore(node => {
const { data: { branchLength } } = node;
const labelWidth = (node.data.name.length + 1) * 8.2 / dy;
node.data.branchLength = parseFloat(branchLength.toFixed(10));
node.data.width = 0;
node.data.lengthFromRoot = (node.parent && node.parent.data.lengthFromRoot)
? parseFloat((node.parent.data.lengthFromRoot + branchLength).toFixed(10))
: branchLength;
phyloTreeNode.eachBefore(currNode => {
const { data: { branchLength, node, link } } = currNode;
currNode.data.node = setNodeInfo(node);
currNode.data.link = setLinkInfo(link);
const labelWidth = (currNode.data.name.length * 8 + currNode.data.node.r) / dy;
currNode.data.branchLength = parseFloat(branchLength.toFixed(10));
currNode.data.width = 0;
node.data.labelWidth = labelWidth;
maxLengthFromRoot = (node.data.lengthFromRoot >= maxLengthFromRoot) ? node.data.lengthFromRoot : maxLengthFromRoot;
maxLabel = (node.data.labelWidth >= maxLabel) ? node.data.labelWidth : maxLabel;
currNode.data.labelWidth = labelWidth;
currNode.data.lengthFromRoot = (currNode.parent && currNode.parent.data.lengthFromRoot)
? parseFloat((currNode.parent.data.lengthFromRoot + branchLength).toFixed(10))
: branchLength;
maxLengthFromRoot = (currNode.data.lengthFromRoot >= maxLengthFromRoot)
? currNode.data.lengthFromRoot
: maxLengthFromRoot;
// console.log(currNode.y);
});
return { maxLengthFromRoot, maxLabel }
return maxLengthFromRoot
}
function setRelativePosition(
root: d3.HierarchyNode<RawPhyloTreeNode>,
maxLengthFromRoot: number,
......@@ -67,10 +76,12 @@ export default function () {
return node.x;
}
}
maxLengthFromRoot += nodeWithPoint.data.node.r / dy;
return {
pointPhylotreeRoot: nodeWithPoint.eachAfter((node) => {
// node.data.labelWidth = node.data.labelWidth;
node.y = node.data.lengthFromRoot / maxLengthFromRoot;
node.y = node.data.lengthFromRoot / maxLengthFromRoot;
// console.log(node.y);
node.data.width = node.data.labelWidth + node.y;
node.x = computeXposition(node);
leftNode = (node.x <= leftNode.x) ? node : leftNode;
......@@ -92,23 +103,24 @@ export default function () {
deepestNodeWithLabel: d3.HierarchyPointNode<PhyloTreeNode>
) {
const margin = leftNode === rightNode ? 1 : separation(leftNode, rightNode) / 2;
const marginTop = pointPhylotreeRoot.data.node.r + pointPhylotreeRoot.data.link.strokeWidth / 2;
const tx = margin - leftNode.x;
// Reajust the position after adding the margin (s)
const kx = dx / (rightNode.x + margin + tx);
const ky = parseFloat((dy * (1 - deepestNodeWithLabel.data.labelWidth) / deepestNodeWithLabel.y).toFixed(10)
);
// Walk that computes the label size in order to get
// a new coef
if (nodeSize) {
pointPhylotreeRoot.eachBefore(function (node) {
node.x = (node.x + tx) * dx;
node.y = parseFloat((node.y * ky).toFixed(1));
node.y = parseFloat((node.y * ky).toFixed(1) + marginTop) ;
})
} else {
pointPhylotreeRoot.eachBefore(function (node) {
node.x = parseFloat(((node.x + tx) * kx).toFixed(1));
node.y = parseFloat((node.y * ky).toFixed(1));
node.y = parseFloat((node.y * ky).toFixed(1)) + marginTop;
node.data.width *= ky;
node.data.labelWidth *= ky;
});
......@@ -118,6 +130,41 @@ export default function () {
return pointPhylotreeRoot;
}
function setNodeInfo(node: PartialNodeInfo | undefined) {
let newNode;
if (node) {
newNode = {
r: (node.r != undefined) ? node.r : defaultNodeR,
fill: (node.fill != undefined) ? node.fill : defaultNodeFill,
strokeWidth: (node.strokeWidth != undefined) ? node.strokeWidth : defaultNodeStrokeWidth
}
}
else {
newNode = {
r: defaultNodeR,
fill: defaultNodeFill,
strokeWidth: defaultNodeStrokeWidth
}
}
return newNode;
}
function setLinkInfo(link: PartialLinkInfo | undefined) {
let newLink;
if (link) {
newLink = {
strokeColor: (link.strokeColor != undefined) ? link.strokeColor : defaultLinkColor,
strokeWidth: (link.strokeWidth != undefined) ? link.strokeWidth : defaultLinkWidth
}
}
else {
newLink = {
strokeColor: defaultLinkColor,
strokeWidth: defaultLinkWidth
}
}
return newLink;
}
// PUBLIC
phylogram.nodeSize = function (size: [number, number]) {
......@@ -133,8 +180,8 @@ export default function () {
return phylogram;
}
phylogram.separation = function <T>(
separationCb: (a: d3.HierarchyNode<T>, b: d3.HierarchyNode<T>) => number
phylogram.separation = function (
separationCb: (a: d3.HierarchyPointNode<PhyloTreeNode>, b: d3.HierarchyPointNode<PhyloTreeNode>) => number
) {
separation = separationCb;
}
......
......@@ -4,4 +4,11 @@ export function defaultSeparation<T>(
): number {
return 1;
// return (a && b && a.parent === b.parent) ? 1 : 2;
}
\ No newline at end of file
}
export const defaultNodeR = 4;
export const defaultNodeFill = "#000";
export const defaultNodeStrokeWidth = 2;
export const defaultLinkColor = "#696969";
export const defaultLinkWidth = 2;
import { ScaleLinear } from "d3-scale";
import { HierarchyNode } from "d3";
export type Strand = "+" | "-";
export interface GeneData {
......@@ -79,18 +78,40 @@ export interface RawPhyloTreeNode {
name: string,
branchLength: number,
children?: RawPhyloTreeNode[],
// lengthFromRoot?: number,
nodes?: {
r?: number,
fill?: string,
strokeWidth?: number
}
node?: PartialNodeInfo,
link?: PartialLinkInfo
}
export type PartialNodeInfo = {
r?: number,
fill?: string,
strokeWidth?: number
}
export interface PhyloTreeNode extends RawPhyloTreeNode {
lengthFromRoot: number
// leftForNode: number,
labelWidth: number,
width: number
export type PartialLinkInfo = {
strokeWidth?: number,
strokeColor?: string
}
export interface PhyloTreeNode {
name: string;
branchLength: number;
lengthFromRoot: number;
labelWidth: number;
width: number;
children?: RawPhyloTreeNode[];
node: {
r: number;
fill: string;
strokeWidth: number;
};
link: {
strokeWidth: number;
strokeColor: string;
};
}
export interface Phylotree {
marginLeft: number,
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment