Looking through the documentation on d3.cluster() I did not see anything that the developer can use to set the type of connecting line. It seems that there is only a curved spline kind of connection, but this is not the most conventional styling for dendrograms -- at least for my situation. What I'm after is a vertically oriented, 90 degree angle joined nodes:
Question
Judging by the documentation, there is no direct built-in solution, but is there anything that d3.cluster()
can offer to achieve the above result? Or am I better off coding everything from scratch?
Looking through the documentation on d3.cluster() I did not see anything that the developer can use to set the type of connecting line. It seems that there is only a curved spline kind of connection, but this is not the most conventional styling for dendrograms -- at least for my situation. What I'm after is a vertically oriented, 90 degree angle joined nodes:
Question
Judging by the documentation, there is no direct built-in solution, but is there anything that d3.cluster()
can offer to achieve the above result? Or am I better off coding everything from scratch?
3 Answers
Reset to default 3This issue has been asked before in "d3js Tree square". However, I do not really consider this a duplicate as that old question was using D3 v3 and, as it turns out, is not easily adapted to v5. Furthermore, you are explicitly asking for a vertical layout.
Nonetheless, the basic approach stays the same: just use a custom path generator. Building upon the old v3 Block this could be done as follows:
svg.selectAll(".link")
.data(root.links())
.enter().append("path")
.attr("d", elbow); // Appended paths use the custom path generator.
// Custom path generator
function elbow(d) {
return "M" + d.source.x + "," + d.source.y + "H" + d.target.x + "V" + d.target.y;
}
With some minor modifications to work with the v5 API Mike Bostock's demo can be rewritten as a Vertical "Elbow" Dendrogram.
Note, that this is the same approach Mike Bostock used in his Tree of Life notebook where he used multiple custom path generators to create radial layouts.
just had a similar need for creating an R-style dendrogram in d3 (→ JS).
The dendrogram
function (bottom of this answer) produces dendrograms in the following style given the result of a call to ml-hclust
dendrogram(hclust_result, { h: 2.5 })
(Here's an an acpanying notebook discussing hierarchical clustering in JS https://observablehq./@chrispahm/hierarchical-clustering)
function dendrogram(data, options = {}) {
const {
width: width = 420,
height: height = 320,
hideLabels: hideLabels = false,
paddingBottom: paddingBottom = hideLabels ? 20 : 80,
innerHeight = height - paddingBottom,
innerWidth = width - 10,
paddingLeft = 30,
h: cutHeight = undefined,
yLabel: yLabel = "↑ Height",
colors: colors = d3.schemeTableau10,
fontFamily: fontFamily = "Inter, sans-serif",
linkColor: linkColor = "grey",
fontSize: fontSize = 10,
strokeWidth: strokeWidth = 1
} = options;
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, innerHeight])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");
var clusterLayout = d3.cluster().size([width - paddingLeft * 2, innerHeight]);
const root = d3.hierarchy(data);
const maxHeight = root.data.height;
const yScaleLinear = d3
.scaleLinear()
.domain([0, maxHeight])
.range([hideLabels ? innerHeight - 35 : innerHeight, 0]);
const yAxisLinear = d3.axisLeft(yScaleLinear).tickSize(5);
function transformY(data) {
const height = hideLabels ? innerHeight - 15 : innerHeight;
return height - (data.data.height / maxHeight) * height;
}
// traverse through first order children and assign colors
if (cutHeight) {
let curIndex = -1;
root.each((child) => {
if (
child.data.height <= cutHeight &&
child.data.height > 0 &&
child.parent &&
!child.parent.color
) {
curIndex++;
child.color = colors[curIndex];
} else if (child.parent && child.parent.color) {
child.color = child.parent.color;
}
});
}
clusterLayout(root);
// y-axis
svg
.append("g")
.attr("transform", `translate(0, ${hideLabels ? 20 : 0})`)
.append("g")
.attr("class", "axis")
.attr("transform", `translate(${paddingLeft},${hideLabels ? 20 : 0})`)
.call(yAxisLinear)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.append("text")
.attr("x", -paddingLeft)
.attr("y", -20)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.style("font-family", fontFamily)
.text(yLabel)
)
.selectAll(".tick")
.classed("baseline", (d) => d == 0)
.style("font-size", `${fontSize}px`)
.style("font-family", fontFamily);
// Links
root.links().forEach((link) => {
svg
.append("path")
.attr("class", "link")
.attr("stroke", link.source.color || linkColor)
.attr("stroke-width", `${strokeWidth}px`)
.attr("fill", "none")
.attr("transform", `translate(${paddingLeft}, ${hideLabels ? 20 : 0})`)
.attr("d", elbow(link));
});
// Nodes
root.descendants().forEach((desc) => {
/*
svg
.append("circle")
.classed("node", true)
.attr("fill", desc.color)
.attr("cx", desc.x)
.attr("cy", transformY(desc))
.attr("transform", `translate(${paddingLeft})`);
.attr("r", 4);
*/
if (desc.data.isLeaf && !hideLabels) {
svg
.append("text")
//.attr("x", desc.x)
.attr("dx", -5)
.attr("dy", 3)
.attr("text-anchor", "end")
.style("font-size", `${fontSize}px`)
.style("font-family", fontFamily)
.attr(
"transform",
`translate(${desc.x + paddingLeft},${transformY(desc)}) rotate(270)`
)
.text(desc.name || desc.data.index);
}
});
// Custom path generator
function elbow(d) {
return (
"M" +
d.source.x +
"," +
transformY(d.source) +
"H" +
d.target.x +
"V" +
transformY(d.target)
);
}
return svg.node();
}
What worked for me take "Most basic dendrogram in d3.js" https://d3-graph-gallery./graph/dendrogram_basic.html
adjust curve parameters to smth like
.attr("d", function(d) {
return "M" + d.y + "," + d.x
+ "L" + (d.parent.y + 0) + "," + d.x
+ " " + d.parent.y + "," + d.parent.x;
})
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745422062a4627007.html
评论列表(0条)