javascriptreactjstreebinary-treedagre

Transform Tree in Array to display with React Flow and Dagre


I have this tree:

const tree = {
      "1": "root", 
      "children": [
        {
          "2": "similar values", 
          "children": [
            {
              "3": "similar values info", 
              "children": [
                {
                  "4": "similar values", 
                  "children": [
                    {
                      "5": "similar values", 
                      "children": [
                        {
                          "6": "similar values"
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }

And I want to transform the data in this format so I can display with React-Flow (example here: https://reactflow.dev/examples/layouting/

This is the format I want:

[
  {
    id: '1'
  },
  {
    id: '2'
  },
  {
    id: '3'
  },
  {
    id: '4'
  },
  {
    id: '5'
  },
  {
    id: '6'
  },
  { id: 'e12', source: '1', target: '2', type: edgeType, animated: true },
  { id: 'e23', source: '2', target: '3', type: edgeType, animated: true },
  { id: 'e34', source: '3', target: '4', type: edgeType, animated: true },
  { id: 'e45', source: '4', target: '5', type: edgeType, animated: true },
  { id: 'e56', source: '5', target: '6', type: edgeType, animated: true },
];

So ultimately I need to convert it to an array, get all the keys as id and get to find source and destination, based on parent/child structure. I would appreciate any input, this is my current code: (I think I'm getting the parent and the source correctly at least), problem is the target, so a way to find the children.

function getParent(root, id) {
    var node;

    root.some(function (n) {
        if (n.id === id) {
            return node = n;
        }
        if (n.children) {
            return node = getParent(n.children, id);
        }
    });
    return node || null;
}

{ 
 id: 'id',
 source: Object.keys(getParent(tree, id))[0], 
 target: '2',
 type: edgeType,
 animated: true 
}

Solution

  • Creates one object (unassigned), so this would be for one edge only. Also realise that some is not really the right tool. You would need to use find and assign its return value to node (outside of the callback).

    Anyway, searching the parent like that is not the most efficient. You could traverse in the input structure and collect the edges as you go...

    Here is how you could do it:

    const edgeType = "edgeType"; // Dummy
    
    function getNodes({children, ...rest}) {
        const [[id, label]] = Object.entries(rest);
        return [{ id, data: { label }}].concat((children??[]).flatMap(getNodes));
    }
    
    function getEdges({children, ...rest}) {
        const [source] = Object.keys(rest);
        children ??= [];
        return children.map(function ({children, ...rest}) {
            const [target] = Object.keys(rest);
            return {
                id: `e${source}_${target}`,
                source,
                target,
                type: edgeType,
                animated: true
            }
        }).concat(children.flatMap(getEdges));
    }
    
    const tree = { "1": "root", "children": [ { "2": "similar values", "children": [ { "3": "similar values info", "children": [ { "4": "similar values", "children": [ { "5": "similar values", "children": [ { "6": "similar values" } ] } ] } ] } ] } ] };
    const result = getNodes(tree).concat(getEdges(tree));
    console.log(result);

    As in this snippet edgeType is not known, I defined it with a dummy value. You would not do this in your environment.