How to Use D3.js cluster
The cluster layout can be used to display clustering results such as decision trees. For details about preparing data and the data structure, see the hierarchy overview. The demo uses the simplest possible code for explanation.
Example program
Sample code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 hierarchy cluster</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- 1. Prepare styles -->
<style>
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<svg width="800" height="600"></svg>
<script>
// 2. Prepare the data to draw
var width = document.querySelector("svg").clientWidth;
var height = document.querySelector("svg").clientHeight;
var data = {
"name": "A",
"children": [
{ "name": "B" },
{
"name": "C",
"children": [{ "name": "D" }, { "name": "E" }, { "name": "F" }]
},
{ "name": "G" },
{
"name": "H",
"children": [{ "name": "I" }, { "name": "J" }]
},
{ "name": "K" }
]
};
// 3. Convert the data to draw
root = d3.hierarchy(data);
var cluster = d3.cluster()
.size([height, width - 160])
// .nodeSize([50,300]) ;
// .separation(function(a, b) { return(a.parent == b.parent ? 1 : 2); });
cluster(root);
// 4. Configure SVG elements
g = d3.select("svg").append("g").attr("transform", "translate(80,0)");
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", function (d) {
return "M" + d.y + "," + d.x +
"C" + (d.parent.y + 100) + "," + d.x +
" " + (d.parent.y + 100) + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x;
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("transform", function (d) { return "translate(" + d.y + "," + d.x + ")"; })
node.append("circle")
.attr("r", 8)
.attr("fill", "#999");
node.append("text")
.attr("dy", 3)
.attr("x", function (d) { return d.children ? -12 : 12; })
.style("text-anchor", function (d) { return d.children ? "end" : "start"; })
.attr("font-size", "200%")
.text(function (d) { return d.data.name; });
</script>
</body>
</html>
Example code explanation
1. Prepare styles
Configure the link styles.
<style>
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
In this example, node styles are set directly on the SVG elements.
2. Prepare the data to draw
Prepare the data to draw.
var data = {
"name": "A",
"children": [
{ "name": "B" },
{
"name": "C",
"children": [{ "name": "D" }, { "name": "E" }, { "name": "F" }]
},
{ "name": "G" },
{
"name": "H",
"children": [{ "name": "I" }, { "name": "J" }]
},
{ "name": "K" }
]
};
For details about the data structure, see this page.
3. Convert the data to draw
Convert the prepared data into the data structure used for drawing.
root = d3.hierarchy(data);
var cluster = d3.cluster()
.size([height, width - 160])
// .nodeSize([50,300]) ;
// .separation(function(a, b) { return(a.parent == b.parent ? 1 : 2); });
cluster(root);
Two conversions are required: prepared data -> hierarchy data -> data for the drawing type, which is cluster in this example.
First, convert data into the root of the hierarchy data structure.
root = d3.hierarchy(data);
The root is assigned to this variable.
Then call the function that converts it into cluster data.
var cluster = d3.cluster()
.size([height, width - 160])
// .nodeSize([50,300]) ;
// .separation(function(a, b) { return(a.parent == b.parent ? 1 : 2); });
cluster(root);
When root is passed to the function returned by d3.cluster(), the following data is assigned to root.
| Coordinate | Description |
|---|---|
x |
Coordinate in the node arrangement, or sibling, direction. |
y |
Coordinate in the node depth, or parent-child, direction. The root starts at 0. |
In this example, x corresponds to the vertical direction on the screen and y corresponds to the horizontal direction, so pay attention to the coordinate orientation.
The following settings are available on d3.cluster().
| Setting | Description |
|---|---|
cluster.size() |
Sets the size of the structure to draw as a two-element array: [arrangement direction, depth direction].If no argument is specified, it returns the current size. The default is [1, 1]. |
cluster.nodeSize() |
Sets the size of one node as a two-element array: [arrangement direction, depth direction].If no argument is specified, it returns the current size. The default is null.When nodeSize() is null, the size() value above is used.When nodeSize() is specified, the first element is positioned at (0,0). |
cluster.separation() |
Sets the function that determines spacing between adjacent elements. The default is as follows.function separation(a, b) { return a.parent == b.parent ? 1 : 2;}With the default settings used in this example, adjacent nodes with different parents have twice as much spacing as nodes with the same parent. |
Here, size() is used to set the cluster width. Since the first coordinate is calculated as 0 and the terminal coordinate in the depth direction is calculated as the configured width, the cluster width is set smaller than the SVG drawing area’s width.
.size([height, width - 160])
4. Configure SVG elements
g = d3.select("svg").append("g").attr("transform", "translate(80,0)");
Because the first element’s coordinate is set to 0, add a "g" group element and shift the whole drawing in the x direction. Nodes and links are configured inside this group element.
First, configure the SVG elements for links.
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter()
.append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + d.y + "," + d.x +
"C" + (d.parent.y + 100) + "," + d.x +
" " + (d.parent.y + 100) + "," + d.parent.x +
" " + d.parent.y + "," + d.parent.x;
});
The following function is used in the data assignment. It returns nested nodes as an array.
root.descendants()
The order follows the depth direction and then the arrangement direction for the specified node. In this example, the order is A->B->C->G->H->K->D->E->F->I->J. Links are drawn from child nodes toward the root, and because there is no link from the root A, the first node is excluded with slice.
Next, configure the SVG elements for nodes.
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
node.append("circle")
.attr("r", 8)
.attr("fill", "#999");
node.append("text")
.attr("dy", 3)
.attr("x", function(d) { return d.children ? -12 : 12; })
.style("text-anchor", function(d) { return d.children ? "end" : "start"; })
.attr("font-size", "200%")
.text(function(d) { return d.data.name; });
Since each node uses both a 'circle' and a 'text' element, first add a 'g' element and then add 'circle' and 'text' inside it. The position is set on the 'g' element; remember that the cluster depth direction is y and the arrangement direction is x.
The right side of nodes that have many children can become crowded with links, so text is placed on the left for nodes with children and on the right for nodes without children. "text-anchor" is the style that controls the text position.
Closing
The hard parts are that the data must be converted twice and that the y coordinate is the cluster depth direction. The cluster layout aligns terminal nodes at the same position, while the similar tree layout places children with the same parent together. Both can be used with almost the same program.