Skip to content

Instantly share code, notes, and snippets.

@t3o-it
Last active August 1, 2019 17:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save t3o-it/5117844 to your computer and use it in GitHub Desktop.
Save t3o-it/5117844 to your computer and use it in GitHub Desktop.
test

This is a customization of d3.layout.tree(). Each node is composed by a circle, a label and if it has at least one child another circle after the label.

I want to use this chart to show the dependencies between some objects(nodes): imagine that there is a set of permissions belonging to the user that is viewing the chart; some permission are missing for the node A21 so the entire path is marked with a different color.

Features

  • The y position of each node is obtained using d3.layout.tree().nodes(data) method
  • Then the x position is modified using the real size of the label as measure
  • Children sharing the same depth have the same x.
  • The current node is highlighted with a blue rectangle placed under it.
  • There's a custom Spline function because using d3.diagonal the anchor points of the splines in each node were switched: in other words a link path starts from the middle top of the source node and ends to the middle bottom of the target node. The custom function is to fix this behaviour.
<!DOCTYPE html>
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="tree.js"></script>
<style>
*{
font-family: "Trebuchet MS", Helvetica, sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.link.warning{
stroke: orange;
}
</style>
</head>
<body>
<div id="depencency-chart"></div>
<script type="text/javascript">
window.onload = function() {
initDependencyChart();
};
</script>
</body>
</html>
var data = {"id": "17", "name": "ROOT", "layer": "0", "current": "true", "children": [
{"id": "18", "name": "A-child-with-a-long-name", "warning": "true", "layer": "1", "children": [
{"id": "19", "name": "A1", "layer": "2"},
{"id": "20", "name": "A2", "warning": "true","layer": "2", "children": [
{"id": "21", "name": "A21", "warning": "true", "layer": "3"},
{"id": "22", "name": "A22", "layer": "3", "children": [
{"id": "23", "name": "A221", "layer": "4"},
{"id": "26", "name": "A224", "layer": "4"},
{"id": "27", "name": "A225", "layer": "4"}
]}
]}
]},
{"id": "28", "name": "B", "layer": "1"},
{"id": "29", "name": "C", "layer": "1", "children": [
{"id": "30", "name": "C1", "layer": "2"},
{"id": "31", "name": "C2", "layer": "2", "children": [
{"id": "32", "name": "C21", "layer": "3"},
{"id": "33", "name": "C22", "layer": "3"}
]}
]}
]};
var customNodes = new Array(),
tmpNodes,
label_w = 70,
branch_w = 70,
layer_wider_label = new Array(),
depencencyChart;
function initDependencyChart() {
tmpNodes = d3.layout.tree().size([500, 400]).nodes(data);
// Create a svg canvas
depencencyChart = d3.select("#depencency-chart").append("svg:svg")
.attr("width", 800)
.attr("height", 500)
.append("svg:g")
.attr("transform", "translate(40, 30)"); // shift everything to the right
var fakeTxtBox = depencencyChart.append("svg:text")
.attr("id", "fakeTXT")
.attr("text-anchor", "right")
.text(data.name);
layer_wider_label[0] = fakeTxtBox.node().getComputedTextLength();
depencencyChart.select("#fakeTXT").remove();
data.y = getNodeY(data.id);
data.x = 0;
data.depth = parseInt(data.layer);
customNodes.push(data);
prepareNodes(data.children);
//align nodes.
updateNodesXOffset()
drawChart();
}
function updateNodesXOffset(){
var x_offsets = new Array();
x_offsets[0] = 0;
customNodes.forEach(function(node) {
node.x = 0;
if (node.layer > 0) {
node.x = x_offsets[node.layer - 1] + layer_wider_label[node.layer - 1] + branch_w;
x_offsets[node.layer] = node.x;
}
});
}
function getNodeY(id) {
var ret = 0;
tmpNodes.some(function(node) {
if (node.id === id) {
//return x:d3.tree has a vertical layout by default.
ret = node.x;
return;
}
})
return ret;
}
function prepareNodes(nodes) {
nodes.forEach(function(node) {
prepareNode(node);
if (node.children) {
prepareNodes(node.children);
}
});
}
function prepareNode(node) {
node.y = getNodeY(node.id);
//fake element to calculate labels area width.
var fakeTxtBox = depencencyChart.append("svg:text")
.attr("id", "fakeTXT")
.attr("text-anchor", "right")
.text(node.name);
var this_label_w = fakeTxtBox.node().getComputedTextLength();
depencencyChart.select("#fakeTXT").remove();
if (layer_wider_label[node.layer] == null) {
layer_wider_label[node.layer] = this_label_w;
} else {
if (this_label_w > layer_wider_label[node.layer]) {
layer_wider_label[node.layer] = this_label_w;
}
}
// node.x = nodex;
//x will be set
node.depth = parseInt(node.layer);
customNodes.push(node);
}
function customSpline(d) {
var p = new Array();
p[0] = d.source.x + "," + d.source.y;
p[3] = d.target.x + "," + d.target.y;
var m = (d.source.x + d.target.x) / 2
p[1] = m + "," + d.source.y;
p[2] = m + "," + d.target.y;
//This is to change the points where the spline is anchored
//from [source.right,target.left] to [source.top,target.bottom]
// var m = (d.source.y + d.target.y)/2
// p[1] = d.source.x + "," + m;
// p[2] = d.target.x + "," + m;
return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
}
function drawChart() {
customNodes.forEach(function(node) {
var nodeSVG = depencencyChart.append("svg:g")
.attr("transform", "translate(" + node.x + "," + node.y + ")")
if (node.depth > 0) {
nodeSVG.append("svg:circle")
.attr("stroke", node.children ? "#3191c1" : "#269926")
.attr("fill", "#fff")
.attr("r", 3);
}
var txtBox = nodeSVG.append("svg:text")
.attr("dx", 8)
.attr("dy", 4)
.attr("fill", node.current ? "#ffffff" : node.children ? "#3191c1" : "#269926")
.text(node.name)
var txtW = txtBox.node().getComputedTextLength();
if (node.current) {
nodeSVG.insert("rect", "text")
.attr("fill", node.children ? "#3191c1" : "#269926")
.attr("width", txtW + 8)
.attr("height", "20")
.attr("y", "-12")
.attr("x", "5")
.attr("rx", 4)
.attr("ry", 4)
}
if (node.children) {
node.x = node.x + txtW + 20;
//prepare links;
var links = new Array();
node.children.forEach(function(child) {
var st = new Object();
st.source = node;
// st.parent = node;
st.target = child;
st.warning = child.warning;
links.push(st);
});
//draw links (under nodes)
depencencyChart.selectAll("pathlink")
.data(links)
.enter().insert("svg:path", "g")
.attr("class", function(d) {
return d.warning === "true" ? "link warning" : "link"
})
.attr("d", customSpline)
//draw a node at the end of the link
nodeSVG.append("svg:circle")
.attr("stroke", "#3191c1")
.attr("fill", "#fff")
.attr("r", 5.5)
.attr("transform", "translate(" + (txtW + 20) + ",0)");
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment