node_clicked(d) {
d.data.isCollapsed = !d.data.isCollapsed;
this.service.linkedConcepts(d.data.id)
.then(data => {
if (data) {
const items = data as any[];
this.appendData(d.data.id, this.treeData, items);
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.root = d3.hierarchy(this.treeData, d => d.children);
this.update(d);
}
})
.catch(err => {
this.removeFromDataBeingRetrieved(d.data.id);
console.error(err);
});
}
appendData(parentid, node, items) {
if (node.id === parentid) {
items.forEach(item => {
node.children.push({
name: item.Label,
id: item.ConceptID,
isCollapsed: true,
isTopConcept: false,
count: item.LinkedConceptsCount,
children: []
});
});
node.isCollapsed = false;
} else if (node.children) {
node.children.forEach(item => this.appendData(parentid, item, items));
}
}
update(source) {
const treeData = this.treemap(this.root);
// Compute the new tree layout.
const nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(d => d.y = d.depth * 180);
// ****************** Nodes section ***************************
let i = 0;
// Update the nodes...
const node = this.svg.selectAll('g.node')
.data(nodes, function(d) {return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
const nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr('transform', d => 'translate(' + source.y0 + ',' + source.x0 + ')')
.on('click', d => this.node_clicked(d));
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style('stroke', function(d) {
return d.data.count > 0 ? '#fff' : 'steelblue'; })
.style('fill', function(d) {
return d.isCollapsed && d.data.count > 0 ? 'lightsteelblue' : '#fff';
})
.append('title')
.text(d => d.data.count + ' linked articles');
// Add labels for the nodes
nodeEnter.append('text')
.attr('dy', '.35em')
.attr('x', function(d) {
return d.children || d._children ? -13 : 13;
})
.attr('text-anchor', function(d) {
return (d.data.isTopConcept === true) ? 'end' : 'start';
})
.text(function(d) { return d.data.name; })
.call(d => this.wrap(d));
// UPDATE
const nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(this.duration)
.attr('transform', function(d) {
return 'translate(' + d.y + ',' + d.x + ')';
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style('fill', function(d) {
if (d.data.count === 0) {
return '#fff';
} else if ( d.data.isCollapsed === true ) {
return 'lightsteelblue';
} else {
return '#fff';
}
})
.style('stroke-width', '3px')
.style('stroke', function(d) {
return d._children > 0 ? '#fff' : 'lightsteelblue'; })
.attr('cursor', 'pointer');
// Remove any exiting nodes
const nodeExit = node.exit().transition()
.duration(this.duration)
.attr('transform', function(d) {
return 'translate(' + source.y + ',' + source.x + ')';
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
const link = this.svg.selectAll('path.link')
.data(links, d => d['id']);
// Enter any new links at the parent's previous position.
const linkEnter = link.enter().insert('path', 'g')
.attr('class', 'link')
.style('fill', 'none')
.style('stroke', '#ccc')
.style('stroke-width', '3px')
.attr('d', d => this.diagonal({x: source.x, y: source.y},
{x: source.x, y: source.y}));
// UPDATE
const linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(this.duration)
.attr('d', d => this.diagonal(d, d.parent) );
// Remove any exiting links
const linkExit = link.exit().transition()
.duration(this.duration)
.attr('d', d => this.diagonal({x: source.x, y: source.y},
{x: source.x, y: source.y}))
.remove();
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
}