d3 | Format d3 tree | Cell 6 | Search

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.

Run example

npm run import -- "Display d3 tree"

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;

What the code could have been:

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:

  1. Initialization:

  2. Data Preparation:

  3. Node and Link Processing:

  4. Chart Creation:

  5. (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.