This example shows how to interact with D3.js to create a Bi-Directional Tree (a variation of robschmuecker@7880033)
The tree shows the dependencies related to D3 development:
The main logic to pull these dependencies and generating tree data is in [my GitHub repo] (https://github.com/shunpochang/d3tree/tree/master/get_tree_from_git), where package.json and bower.json files (to include both NPM and Bower installation) are crawled to get the top matching libraries.
Tree interaction and display:
For any help/questions, shunpochang at gmail or tag me @shunpochang.
forked from mayblue9's block: D3 Bi-directional Drag and Zoom Tree on D3 development
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg{
cursor: all-scroll;
}
.centralText{
font: 23spx sans-serif;
fill: #222;
font-weight: bold;
}
.downwardNode circle{
fill: #fff;
stroke: #8b4513;
stroke-width: 2.5px;
}
.upwardNode circle {
fill: #fff;
stroke: #37592b;
stroke-width: 2.5px;
}
.downwardNode text,
.upwardNode text {
font: 12px sans-serif;
font-weight:bold;
}
.downwardLink {
fill: none;
stroke: #8b4513;
stroke-width: 3px;
opacity: 0.2;
}
.upwardLink {
fill: none;
stroke: #37592b;
stroke-width: 3px;
opacity: 0.2;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
/**
* Initialize tree chart object and data loading.
* @param {Object} d3Object Object for d3, injection used for testing.
*/
var treeChart = function(d3Object) {
this.d3 = d3Object;
// Initialize the direction texts.
this.directions = ['upward', 'downward'];
};
/**
* Set variable and draw chart.
*/
treeChart.prototype.drawChart = function() {
// First get tree data for both directions.
this.treeData = {};
var self = this;
d3.json('raw_all_tree_data.json', function(error, allData) {
self.directions.forEach(function(direction) {
self.treeData[direction] = allData[direction];
});
self.graphTree(self.getTreeConfig());
});
};
/**
* Get tree dimension configuration.
* @return {Object} treeConfig Object containing tree dimension size
* and central point location.
*/
treeChart.prototype.getTreeConfig = function() {
var treeConfig = {'margin': {'top': 10, 'right': 5, 'bottom': 0, 'left': 30}}
// This will be the maximum dimensions
treeConfig.chartWidth = (960 - treeConfig.margin.right -
treeConfig.margin.left);
treeConfig.chartHeight = (500 - treeConfig.margin.top -
treeConfig.margin.bottom);
treeConfig.centralHeight = treeConfig.chartHeight / 2;
treeConfig.centralWidth = treeConfig.chartWidth / 2;
treeConfig.linkLength = 100;
treeConfig.duration = 200;
return treeConfig;
};
/**
* Graph tree based on the tree config.
* @param {Object} config Object for chart dimension and central location.
*/
treeChart.prototype.graphTree = function(config) {
var self = this;
var d3 = this.d3;
var linkLength = config.linkLength;
var duration = config.duration;
// id is used to name all the nodes;
var id = 0;
var diagonal = d3.svg.diagonal()
.projection(function(d) {return [d.x, d.y]; });
var zoom = d3.behavior.zoom()
.scaleExtent([0.1, 2])
.on('zoom', redraw);
var svg = d3.select('body')
.append('svg')
.attr('width',
config.chartWidth + config.margin.right + config.margin.left)
.attr('height',
config.chartHeight + config.margin.top + config.margin.bottom)
.on('mousedown', disableRightClick)
.call(zoom)
.on('dblclick.zoom', null);
var treeG = svg.append('g')
.attr('transform',
'translate(' + config.margin.left + ',' + config.margin.top + ')');
treeG.append('text').text('D3 Development Dependency')
.attr('class', 'centralText')
.attr('x', config.centralWidth)
.attr('y', config.centralHeight + 5)
.attr('text-anchor', 'middle');
// Initialize the tree nodes and update chart.
for (var d in this.directions) {
var direction = this.directions[d];
var data = self.treeData[direction];
data.x0 = config.centralWidth;
data.y0 = config.centralHeight;
// Hide all children nodes other than direct generation.
data.children.forEach(collapse);
update(data, data, treeG);
}
/**
* Update nodes and links based on direction data.
* @param {Object} source Object for current chart distribution to identify
* where the children nodes will branch from.
* @param {Object} originalData Original data object to get configurations.
* @param {Object} g Handle to svg.g.
*/
function update(source, originalData, g) {
// Set up the upward vs downward separation.
var direction = originalData['direction'];
var forUpward = direction == 'upward';
var node_class = direction + 'Node';
var link_class = direction + 'Link';
var downwardSign = (forUpward) ? -1 : 1;
var nodeColor = (forUpward) ? '#37592b' : '#8b4513';
// Reset tree layout based on direction, since the downward chart has
// way too many nodes to fit in the screen, while we want a symmetric
// view for upward chart.
var nodeSpace = 50;
var tree = d3.layout.tree().sort(sortByDate).nodeSize([nodeSpace, 0]);
if (forUpward) {
tree.size([config.chartWidth, config.chartHeight]);
}
var nodes = tree.nodes(originalData);
var links = tree.links(nodes);
// Offset x-position for downward to view the left most record.
var offsetX = 0;
if (!forUpward) {
var childrenNodes = originalData[
(originalData.children) ? 'children' : '_children'];
offsetX = d3.min([childrenNodes[0].x, 0]);
}
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = downwardSign * (d.depth * linkLength) + config.centralHeight;
d.x = d.x - offsetX;
// Position for origin node.
if (d.name == 'origin') {
d.x = config.centralWidth;
d.y += downwardSign * 25;
}
});
// Update the node.
var node = g.selectAll('g.' + node_class)
.data(nodes, function(d) {return d.id || (d.id = ++id); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', node_class)
.attr('transform', function(d) {
return 'translate(' + source.x0 + ',' + source.y0 + ')'; })
.style('cursor', function(d) {
return (d.children || d._children) ? 'pointer' : '';})
.on('click', click);
nodeEnter.append('circle')
.attr('r', 1e-6);
// Add Text stylings for node main texts
nodeEnter.append('text')
.attr('x', function(d) {
return forUpward ? -10 : 10;})
.attr('dy', '.35em')
.attr('text-anchor', function(d) {
return forUpward ? 'end' : 'start';})
.text(function(d) {
// Text for origin node.
if (d.name == 'origin') {
return ((forUpward) ?
'Dependency of D3' :
'Files dependent on D3'
) + ' [Click to fold/expand all]';
}
// Text for summary nodes.
if (d.repeated) {
return '[Recurring] ' + d.name;
}
return d.name; })
.style('fill-opacity', 1e-6)
.style({'fill': function(d) {
if (d.name == 'origin') {return nodeColor;}
}})
.attr('transform', function(d) {
if (d.name != 'origin') {return 'rotate(-20)';}
})
;
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')'; });
nodeUpdate.select('circle')
.attr('r', 6)
.style('fill', function(d) {
if (d._children || d.children) {return nodeColor;}
})
.style('fill-opacity', function(d) {
if (d.children) {return 0.35;}
})
// Setting summary node style as class as mass style setting is
// not compatible to circles.
.style('stroke-width', function(d) {
if (d.repeated) {return 5;}
});
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.x + ',' + source.y + ')'; })
.remove();
nodeExit.select('circle')
.attr('r', 1e-6);
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// Update the links.
var link = g.selectAll('path.' + link_class)
.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_class)
.attr('d', function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// 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;
});
/**
* Tree function to toggle on click.
* @param {Object} d data object for D3 use.
*/
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}else {
d.children = d._children;
d._children = null;
// expand all if it's the first node
if (d.name == 'origin') {d.children.forEach(expand);}
}
update(d, originalData, g);
}
}
// Collapse and Expand can be modified to include touched nodes.
/**
* Tree function to expand all nodes.
* @param {Object} d data object for D3 use.
*/
function expand(d) {
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}
}
/**
* Tree function to collapse children nodes.
* @param {Object} d data object for D3 use.
*/
function collapse(d) {
if (d.children && d.children.length != 0) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
/**
* Tree function to redraw and zoom.
*/
function redraw() {
treeG.attr('transform', 'translate(' + d3.event.translate + ')' +
' scale(' + d3.event.scale + ')');
}
/**
* Tree functions to disable right click.
*/
function disableRightClick() {
// stop zoom
if (d3.event.button == 2) {
console.log('No right click allowed');
d3.event.stopImmediatePropagation();
}
}
/**
* Tree sort function to sort and arrange nodes.
* @param {Object} a First element to compare.
* @param {Object} b Second element to compare.
* @return {Boolean} boolean indicating the predicate outcome.
*/
function sortByDate(a, b) {
// Compare the individuals based on participation date
//(no need to compare when there is only 1 summary)
var aNum = a.name.substr(a.name.lastIndexOf('(') + 1, 4);
var bNum = b.name.substr(b.name.lastIndexOf('(') + 1, 4);
// Sort by date, name, id.
return d3.ascending(aNum, bNum) ||
d3.ascending(a.name, b.name) ||
d3.ascending(a.id, b.id);
}
};
var d3GenerationChart = new treeChart(d3);
d3GenerationChart.drawChart();
</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js