I am working on an angular application with d3. My code is as follows.
var treeData = [{
"name": "MD",
"children": [{
"name": "Professional",
"children": [{
"name": "Third A",
"children": [{
"name": "Fourth A",
"children": [{
"name": "Fifth A"
}, {
"name": "Fifth B"
}, {
"name": "Fifth C"
}, {
"name": "Fifth D"
}]
}, {
"name": "Fourth B"
}, {
"name": "Fourth C"
}, {
"name": "Fourth D"
}]
}, {
"name": "Third B"
}]
}, {
"name": "Leader",
"children": [{
"name": "Third C"
}, {
"name": "Third D"
}]
}, {
"name": "Advocate",
"children": [{
"name": "Third E"
}, {
"name": "Third F"
}]
}, {
"name": "Clinician",
"children": [{
"name": "Third G"
}, {
"name": "Third H"
}, ]
}, ]
}];
var colourScale = d3.scale.ordinal()
.domain(["MD", "Professional", "Leader", "Advocate", "Clinician"])
.range(["#6695c8", "#cd3838", "#d48440", "#a8ba5f", "#63b7c0"]);
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1200 - margin.right - margin.left,
height = 650 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
root.x0 = height / 2;
root.y0 = 0;
update(root);
d3.select(self.frameElement).style("height", "500px");
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
console.log('UPDATE')
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 200;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "#C0C0C0" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
nodeEnter.append('foreignObject').attr('width', '20')
.attr("x", 10)
.attr("y", 1)
.attr('height', '20').append('xhtml:input')
.attr('type', 'checkbox')
.attr("id", d => `checkbox-${d.id}`)
//.attr("fill","none")
//.style("opacity","1")
// An on click function for the checkboxes
.on("click", d => {
if (d.children) {
d.children.forEach(child => {
const cb = d3.select(`#checkbox-${child.id}`);
//console.log('CB: ', cb.node());
cb.node().checked = d3.event.target.checked;
cb.attr('disabled', d3.event.target.checked ? true : null);
})
}
else {
if (d3.event.target.checked) {
d.parent.children.forEach(child => {
console.log('CID: ', child.id, d.id);
if (child.id !== d.id) {
const cb = d3.select(`#checkbox-${child.id}`);
console.log('CB: ', cb.node())
cb.node().checked = false;
}
});
}
}
d3.event.stopPropagation();
//console.log(d);
//console.log(d3.event.target.checked);
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 10)
.attr("fill-opacity", "0.7")
.attr("stroke-opacity", "1")
.style("fill", function(d) {
return (typeof d._children !== 'undefined') ? (colourScale(findParent(d))) : '#FFF';
})
.style("stroke", function(d) {
return colourScale(findParent(d));
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke-width", function(d) {
return 1;
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.attr("opacity", "0.3")
.style("stroke", function(d) {
return colourScale(findParentLinks(d));
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function findParent(datum) {
if (datum.depth < 2) {
return datum.name
} else {
return findParent(datum.parent)
}
}
function findParentLinks(datum) {
if (datum.target.depth < 2) {
return datum.target.name
} else {
return findParent(datum.target.parent)
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
I am facing following problem:
If a collapsed node has children, and its parent is getting checked, the node itself is checked as well, but its children cannot be checked because they are not visible. The checking should work recursively: if a node is checked, all its descendants should be checked and disabled as well regardless their state (expanded or collapsed).
How can I do this?