This code prepares data and sets up the structure for a D3.js visualization of a hierarchical tree, likely representing a file system or organizational chart, but lacks the code to render the visual elements.
npm run import -- "Display d3 tree"
var D3Node = require('d3-node');
var margin = {top: 25, right: 25, bottom: 25, left: 25},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//var WIDTH_BETWEEN_NODES_X = 100;
var WIDTH_BETWEEN_NODES_X = 10;
var WIDTH_BETWEEN_NODES_Y = 20;
function displayBranches(nodes) {
var d3n = new D3Node(); // initializes D3 with container element
var d3 = d3n.d3;
var visited = [];
var root = d3.hierarchy({ name: '', children: nodes }, d => {
if(visited.indexOf(d) > -1) {
return [];
}
visited.push(d);
return d.children;
})
.sum(d => 1)
.sort((a, b) => b.data.time - a.data.time || b.value - a.value)
//)
.descendants();
var branches = root
.map(r => (r.data.branch || '').replace('HEAD -> ', '').trim().split(/\s*,\s*/igm))
.reduce((acc, r) => acc.concat(r), [])
.filter((b, i, arr) => arr.indexOf(b) === i)
root.forEach(r => {
r.name = r.data.name;
r.index = r.data.index;
r.branch = r.data.branch;
})
var nodes = d3.hierarchy(root[0], d => d.children)
.descendants();
var nodeNames = nodes.map(n => n.data.name);
nodes = nodes
.filter((n, i, arr) => nodeNames.indexOf(n.data.name) === i)
//.map((n, i) => {
// Object.assign(n.data, {index: i})
// return n;
//})
.slice(1);
var links = nodes.filter(n => n !== null && n.depth > 1);
var fill = d3.scaleOrdinal(d3.schemeCategory20);
var svg = d3n.createSVG(
branches.length * WIDTH_BETWEEN_NODES_X + margin.left + margin.right,
(nodes.length + 1) * WIDTH_BETWEEN_NODES_Y + margin.top + margin.bottom)
var g = svg.append('g');
var x = d3.scaleLinear().range([branches.length * WIDTH_BETWEEN_NODES_X, 0]);
var y = d3.scaleLinear().range([(nodes.length + 1) * WIDTH_BETWEEN_NODES_Y, 0]);
x.domain([0, branches.length]);
y.domain([0, nodes.length]);
function branchIndex(d) {
var result = Math.min.apply(null, (d.data.branch || '')
.split(/\s*,\s*/ig)
.map(b => branches.indexOf(b))
.filter(i => i > -1));
if (!isFinite(result)) {
result = d.depth;
}
return result;
};
g.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('fill', 'none')
.attr('stroke', 'rgba(0,0,0,0.3)')
.attr('stroke-width', 3)
.attr('d', d => `
M${x(branchIndex(d))},${y(d.data.index)}
C${x((branchIndex(d) + branchIndex(d.parent)) / 2.0)},
${y(d.data.index)} ${x((branchIndex(d) + branchIndex(d.parent)) / 2.0)},
${y(d.parent.data.index)} ${x(branchIndex(d.parent))},
${y(d.parent.data.index)}`);
// Declare the nodes
var node = g.selectAll('g.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
.style('fill', function (d, i) {
return fill(d.data.branch);
})
.attr('transform', d => `translate(${x(branchIndex(d))},${y(d.data.index)})`);
node.append('circle')
.attr('class', d => (d.data).isAux ? 'node-aux-route' : 'node-route')
.attr('r', 6);
node.append('text')
.attr('x', (d) => 13)
.attr('dy', '.35em')
.attr('text-anchor', 'start')
.attr('style', 'text-shadow:0 0 1px rgba(0,0,0,1), 0 0 1px rgba(0,0,0,1), 0 0 1px rgba(0,0,0,1);')
.text(d => d.data.name + (d.data.branch && (typeof d.children === 'undefined'
|| !d.children || d.children.filter(
c => c.data.branch == d.data.branch) == 0) ? (' ( ' + d.data.branch + ' ) ') : ''))
.attr('class', 'monospace');
// reset transform
g.attr('transform', 'translate(0, 0)');
g.attr(
'transform',
'translate(' + (margin.left) + ',' + (margin.top) + ')')
return d3n.svgString();
}
module.exports = displayBranches;
const d3 = require('d3-node');
class BranchVisualizer {
constructor() {
this.margin = { top: 25, right: 25, bottom: 25, left: 25 };
this.width = 960;
this.height = 500;
this.WIDTH_BETWEEN_NODES_X = 10;
this.WIDTH_BETWEEN_NODES_Y = 20;
this.fillScale = d3.scaleOrdinal(d3.schemeCategory20);
}
displayBranches(nodes) {
this.width -= this.margin.left + this.margin.right;
this.height -= this.margin.top + this.margin.bottom;
const d3n = new d3();
const svg = d3n.createSVG(
(nodes.length + 1) * this.WIDTH_BETWEEN_NODES_X + this.margin.left + this.margin.right,
(nodes.length + 1) * this.WIDTH_BETWEEN_NODES_Y + this.margin.top + this.margin.bottom
);
const g = svg.append('g');
const root = this.createHierarchy(nodes);
const branches = root
.descendants()
.map(d => d.data.branch || '')
.map(branch => branch.replace('HEAD -> ', '').trim().split(/\s*,\s*/igm))
.reduce((acc, r) => acc.concat(r), [])
.filter((b, i, arr) => arr.indexOf(b) === i);
this.createLinks(root, g, branches);
this.createNodes(root, g, branches, svg);
return d3n.svgString();
}
createHierarchy(nodes) {
const visited = [];
return nodes.reduce((acc, node) => {
const children = this.getVisibleChildren(nodes, node, visited);
if (children.length > 0) {
acc.children = acc.children || [];
acc.children.push({...node, children });
}
return acc;
}, { name: '', children: [] });
}
getVisibleChildren(nodes, node, visited) {
const children = node.children || [];
return children.filter(child =>!visited.includes(child) && child.depth > 0);
}
createLinks(root, g, branches) {
const links = root.descendants().filter(n => n.depth > 1);
const x = d3.scaleLinear().range([branches.length * this.WIDTH_BETWEEN_NODES_X, 0]).domain([0, branches.length]);
const y = d3.scaleLinear().range([(root.depth + 1) * this.WIDTH_BETWEEN_NODES_Y, 0]).domain([0, root.depth + 1]);
g.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('fill', 'none')
.attr('stroke', 'rgba(0,0,0,0.3)')
.attr('stroke-width', 3)
.attr('d', d => `
M${x(this.getBranchIndex(branches, d.data.branch))},${y(d.depth)}
C${x((this.getBranchIndex(branches, d.data.branch) + this.getBranchIndex(branches, d.parent.data.branch)) / 2.0)},
${y(d.depth)} ${x((this.getBranchIndex(branches, d.data.branch) + this.getBranchIndex(branches, d.parent.data.branch)) / 2.0)},
${y(d.parent.depth)}, ${x(this.getBranchIndex(branches, d.parent.data.branch))},
${y(d.parent.depth)}`);
function getBranchIndex(branches, branch) {
const index = branches.findIndex(b => b === branch);
return index === -1? d.depth : index;
}
}
createNodes(root, g, branches, svg) {
const node = g.selectAll('g.node')
.data(root.descendants())
.enter().append('g')
.attr('class', 'node')
.style('fill', d => this.fillScale(d.data.branch || ''))
.attr('transform', d => `translate(${this.getBranchIndex(branches, d.data.branch)}, ${d.depth * this.WIDTH_BETWEEN_NODES_Y})`);
node.append('circle')
.attr('class', d => (d.data.isAux? 'node-aux-route' : 'node-route'))
.attr('r', 6);
node.append('text')
.attr('x', 13)
.attr('dy', '.35em')
.attr('text-anchor','start')
.attr('style', 'text-shadow:0 0 1px rgba(0,0,0,1), 0 0 1px rgba(0,0,0,1), 0 0 1px rgba(0,0,0,1);')
.text(d => d.data.name + (d.data.branch && d.children.filter(c => c.data.branch == d.data.branch).length == 0? (' ('+ d.data.branch +') ') : ''));
function getBranchIndex(branches, branch) {
const index = branches.findIndex(b => b === branch);
return index === -1? d.depth : index;
}
}
}
module.exports = (function() {
const visualizer = new BranchVisualizer();
return visualizer.displayBranches;
})();
This code generates a D3.js visualization of a hierarchical tree structure, likely representing a file system or organizational hierarchy.
Here's a breakdown:
Initialization:
d3-node
library for using D3.js in a Node.js environment.Data Preparation:
displayBranches
function that takes an array of nodes as input.Node and Link Processing:
index
, branch
, and name
.Chart Creation:
(Incomplete):
In essence, this code sets up the foundation for a D3.js hierarchical tree visualization, defining scales, axes, and the basic structure of the chart. It's missing the code to actually draw the nodes, links, and other visual elements.