javascriptd3.jsdendrogram

Convert hierarchical text (with hyphens) into flare data for D3.js


Trying to create the following:

enter image description here

...using this text data:

var inputText = `
- a
  - - b_1
  - - - c_1
  - - - c_2
- b
  - - b_2
  - - - d_1
  - - - d_2
`;

Here is the code:

JS:

function parseHierarchicalText(text) {
var lines = text.split('\n');

function parseNode(index, depth) {
    var node = {
        name: lines[index].trim().replace(/-/g, '').trim(),
        children: []
    };
    index++;
    while(index < lines.length && lines[index].lastIndexOf('-') / 2 > depth) {
        var result = parseNode(index, depth + 1);
        node.children.push(result.node);
        index = result.index;
    }
    return {
        node: node,
        index: index
    };
}
var flareData = [];
var index = 0;
while(index < lines.length) {
    var result = parseNode(index, 0);
    flareData.push(result.node);
    index = result.index + 1;
}
return {
    name: 'Root',
    children: flareData
};
}
var inputText = `
- a
  - - b_1
  - - - c_1
  - - - c_2
- b
  - - b_2
  - - - d_1
  - - - d_2
`;
var data = parseHierarchicalText(inputText);
// Set the dimensions and margins of the diagram
var margin = {
        top: 20,
        right: 90,
        bottom: 30,
        left: 90
    },
    width = 800 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
// Appending svg to the dendrogram div
var svg = d3.select("#dendrogram").append("svg").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Create a hierarchical cluster layout
var hierarchy = d3.hierarchy(data, function(d) {
    return d.children; // specify children accessor
});
var cluster = d3.cluster().size([height, width - 100]);
// Assigns the x and y position for the nodes
var root = cluster(hierarchy);
// Draw links between nodes
svg.selectAll('path').data(root.links()).enter().append('path').attr('class', 'link').attr('d', d3.linkHorizontal().x(function(d) {
    return d.y;
}).y(function(d) {
    return d.x;
}));
// Draw nodes
var nodes = svg.selectAll('g.node').data(root.descendants()).enter().append('g').attr('class', function(d) {
    return 'node' + (d.children ? ' node--internal' : ' node--leaf');
}).attr('transform', function(d) {
    return 'translate(' + d.y + ',' + d.x + ')';
});
nodes.append('circle').attr('r', 7);
nodes.append('text').attr('dy', '.35em').attr('x', function(d) {
    return d.children ? -13 : 13;
}).style('text-anchor', function(d) {
    return d.children ? 'end' : 'start';
}).text(function(d) {
    return d.data.name;
});

But instead it produces this:

enter image description here

Here is a codepen.


Solution

  • FWIW, here's a function that appears to parse your input correctly:

    function parseHierarchicalText(text) {
        let root = {name: 'root', children: []}
        let stack = [root]
    
        for (let line of text.trim().split('\n')) {
            let parts = line.match(/([- ]+)(.+)/)
            let level = parts[1].match(/-/g).length
            while (level < stack.length)
                stack.pop()
            let node = {name: parts[2], children: []}
            stack.at(-1).children.push(node)
            stack.push(node)
        }
    
        return root
    }
    
    //
    
    var inputText = `
    - a
      - - b_1
      - - - c_1
      - - - c_2
    - b
      - - b_2
      - - - d_1
      - - - d_2
    `;
    
    
    r = parseHierarchicalText(inputText)
    console.log(JSON.stringify(r, 0, 4))