function Tree(
data,
{
path,
id = Array.isArray(data) ? (d) => d.id : null,
parentId = Array.isArray(data) ? (d) => d.parentId : null,
children,
tree = d3.tree,
sort,
label,
title,
link,
linkTarget = "_blank",
width = 640,
height,
r = 4,
padding = 10,
fill = "#999",
fillOpacity,
stroke = "#555",
strokeWidth = 3,
strokeOpacity = 0.4,
strokeLinejoin,
strokeLinecap = "round",
halo = "#fff",
haloWidth = 3,
curve = d3.curveBumpX,
spaceBetween = 60,
isVertical = true,
fontSize = 14,
nodeRelSize = 4
} = {}
) {
const zoom = d3.zoom().on('zoom', handleZoom);;
console.log('->>>>>>>>>>>><<', label)
// If id and parentId options are specified, or the path option, use d3.stratify
// to convert tabular data to a hierarchy; otherwise we assume that the data is
// specified as an object {children} with nested objects (a.k.a. the “flare.json”
// format), and use d3.hierarchy.
const root = d3.hierarchy(data, children);
// Sort the nodes.
if (sort != null) root.sort(sort);
// Compute children from root parent
const rootChildren = root.children;
console.log(root.links());
// Compute labels and titles.
const descendants = root.descendants();
const L = descendants.map((d) => d.data.name);
// Compute the layout.
const dx = spaceBetween;
const dy = width / (root.height + padding);
if (isVertical) {
}
console.log("size", root.data.size);
console.log('------->', root)
tree().nodeSize([dx, dy])(root);
console.log('------->', root)
//tree(root);
let y0 = Infinity;
let x0 = Infinity;
/*
// Center the tree.
if (isVertical) {
let y1 = -y0;
root.each((d) => {
if (d.y > y1) y1 = d.y;
if (d.y < y0) y0 = d.y;
});
// Compute the default height.
if (width === undefined) width = y1 - y0 + dy * 2;
} else {
let x1 = -x0;
root.each((d) => {
if (d.x > x1) x1 = d.x;
if (d.x < x0) x0 = d.x;
});
// Compute the default height.
if (width === undefined) width = x1 - x0 + dx * 2;
}
*/
// Use the required curve
if (typeof curve !== "function") throw new Error(`Unsupported curve`);
/*
* Build path with arcs instead of curve line
*/
const roundedPath = (x1, y1, x2, y2) => {
let path = `
M${x1} ${y1}`;
if (y1 !== y2) {
var middlePoint1 = { x: x1 + (x2 - x1) / 2, y: y1 };
var middlePoint2 = { x: x1 + (x2 - x1) / 2, y: y2 };
if (isVertical) {
middlePoint1 = { x: x1, y: y1 + (y2 - y1) / 2 };
middlePoint2 = { x: x2, y: y1 + (y2 - y1) / 2 };
}
path = path.concat(
"",
buildPath(
x1,
y1,
middlePoint1.x,
middlePoint1.y,
middlePoint2.x,
middlePoint2.y
)
);
path = path.concat(
"",
buildPath(
middlePoint1.x,
middlePoint1.y,
middlePoint2.x,
middlePoint2.y,
x2,
y2
)
);
}
return path.concat(
"",
`
L${x2} ${y2}`
);
};
const buildPath = (x1, y1, x2, y2, x3, y3) => {
const angle = Math.PI / 2;
const shortestRay = Math.min(
Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
Math.sqrt(Math.pow(x2 - x3, 2) + Math.pow(y2 - y3, 2))
);
let spaceToUse = spaceBetween;
if (isVertical) {
if (Math.abs(x3 - x1) < spaceBetween) {
spaceToUse = Math.abs(x3 - x1);
}
} else {
if (Math.abs(y3 - y1) < spaceBetween) {
spaceToUse = Math.abs(y3 - y1);
}
}
const radiusToUse = Math.min(
spaceToUse / 2,
shortestRay * Math.tan(angle / 2)
);
const distanceToTangentPoint = Math.abs(radiusToUse / Math.tan(angle / 2));
const determinant = (x2 - x1) * (y2 - y3) - (x2 - x3) * (y2 - y1);
const sweepFlag = determinant < 0 ? 1 : 0;
const anchorIn = alongSegment(
{ x: x2, y: y2 },
{ x: x1, y: y1 },
distanceToTangentPoint
);
const anchorOut = alongSegment(
{ x: x2, y: y2 },
{ x: x3, y: y3 },
distanceToTangentPoint
);
return `
L${anchorIn.x} ${anchorIn.y}
A${radiusToUse} ${radiusToUse} 0 0 ${sweepFlag} ${anchorOut.x} ${anchorOut.y}`;
};
// Manage highlight on hover
const colorAncestors = (data) => {
if (data) {
d3.selectAll("circle")
.filter(function (d, i) {
if (isVertical) {
return (
d3.select(this).attr("transform") ==
`translate(${data.x},${data.y})`
);
} else {
return (
d3.select(this).attr("transform") ==
`translate(${data.y},${data.x})`
);
}
})
//.attr("r", 6)
.attr("stroke", "black")
.raise();
d3.selectAll("text")
.filter(function (d, i) {
if (isVertical) {
return (
d3.select(this).attr("transform") ==
`translate(${data.x},${data.y})`
);
} else {
return (
d3.select(this).attr("transform") ==
`translate(${data.y},${data.x})`
);
}
})
.attr("font-weight", "bold")
.attr("fill", "black")
.raise();
d3.selectAll("path")
.filter(function (d, i) {
if (isVertical) {
return d3.select(this).attr("d").endsWith(`L${data.x} ${data.y}`);
} else {
return d3.select(this).attr("d").endsWith(`L${data.y} ${data.x}`);
}
})
.attr("stroke", "red")
.raise();
colorAncestors(data.parent);
}
};
/*
* Event management
*/
const onMouseOver = (event, data) => {
d3.selectAll("path").attr("stroke", "#ffd399");
d3.selectAll("circle").attr("stroke", "grey");
d3.selectAll("text").attr("fill", "grey");
colorAncestors(data);
};
const onMouseOut = (event, data) => {
//d3.selectAll("circle").attr("r", 4).attr("stroke", "black").raise();
d3.selectAll("circle").attr("stroke", "black").raise();
d3.select(event.link).attr("stroke-width", 3);
d3.selectAll("path").attr("stroke", "orange");
d3.selectAll("text").attr("font-weight", "normal").attr("fill", "black");
};
/*
* Render svg
*/
const svg = d3
.create("svg")
.attr(
"viewBox",
isVertical
? [(-dx * padding), y0 - dy, width, height]
: [(-dy * padding) / 2, x0 - dx, width, height]
)
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("width", width)
.attr("height", height)
.attr("stroke-width", 2)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.call(zoom);
// Append paths
svg
.append("g")
.attr("fill", "none")
.attr("stroke", "orange")
.attr("stroke-opacity", 1)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-width", strokeWidth)
.selectAll("path")
.data(root.links())
.join("path")
.attr("d", (l) => {
if (isVertical) {
return roundedPath(l.source.x, l.source.y, l.target.x, l.target.y);
} else {
return roundedPath(l.source.y, l.source.x, l.target.y, l.target.x);
}
});
// Append nodes as circles
const node = svg
.append("g")
.selectAll("circle")
.data(root.descendants())
.join("circle")
.attr("fill", "white")
.attr("r", (d) => d.data.name == "home" ? nodeSize(nodeRelSize) : nodeSize(d.data.size))
.attr("stroke", "black")
.attr("id", (l) => l.data.size)
.attr("transform", (d) => {
if (isVertical) {
//return `translate(${d.x},${d.y})`;
return `translate(${d.x},${d.y})`;
} else {
//return `translate(${d.y},${d.x})`;
return `translate(${d.y},${d.x})`;
}
})
.style("cursor", "pointer")
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut);
// Append nodes title if exists
if (L)
svg
.append("g")
.selectAll("circle")
.data(root.descendants())
.join("text")
.attr("transform", (d) => {
if (isVertical) {
return `translate(${d.x},${d.y})`;
} else {
return `translate(${d.y},${d.x})`;
}
})
.attr("dy", "0.32em")
.attr("y", (d) => (d.children && isVertical ? -16 : 16))
.attr("text-anchor", "middle") // change to end or start if horizontal
.attr("paint-order", "stroke")
.attr("stroke", halo)
.attr("font-size", fontSize)
.attr("stroke-width", haloWidth)
.text((d, i) => L[i])
.style("cursor", "pointer")
.on("mouseover", onMouseOver)
.on("mouseout", onMouseOut);
/*
function drag(simulation) {
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}
return d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
}
*/
function handleZoom(e) {
d3.selectAll('g')
.attr('transform', e.transform);
}
function initZoom() {
d3.select('svg')
.call(zoom);
}
initZoom();
return svg.node();
}