Search code examples
javascripttreetabulatorflowchart

JavaScript converting tree to a nested array


I have an flowchart input using jquery flowchart library like so:

Flowchart

I get the data representation as this:

{
  "operators": {
    "0": {
      "properties": {
        "title": "Start",
        "inputs": {},
        "outputs": {
          "outs": {
            "label": "Output (:i)",
            "multiple": true
          }
        },
        "class": "start-operator"
      },
      "top": 0,
      "left": 0
    },
    "1": {
      "properties": {
        "title": "End",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {},
        "class": "end-operator"
      },
      "top": null,
      "left": null
    },
    "37": {
      "properties": {
        "title": "CHROM",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 300,
      "top": 0
    },
    "38": {
      "properties": {
        "title": "CHROM",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 580,
      "top": 0
    },
    "39": {
      "properties": {
        "title": "REF",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 920,
      "top": 0
    },
    "40": {
      "properties": {
        "title": "REF",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 300,
      "top": 100
    },
    "41": {
      "properties": {
        "title": "REF",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 300,
      "top": 200
    },
    "42": {
      "properties": {
        "title": "REF",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 620,
      "top": 140
    },
    "43": {
      "properties": {
        "title": "POS",
        "inputs": {
          "ins": {
            "label": "Input (:i)",
            "multiple": true
          }
        },
        "outputs": {
          "output": {
            "label": "Output"
          }
        }
      },
      "left": 740,
      "top": 320
    }
  },
  "links": {
    "0": {
      "fromOperator": "0",
      "fromConnector": "outs",
      "fromSubConnector": 0,
      "toOperator": 37,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #e53935"
    },
    "1": {
      "fromOperator": "0",
      "fromConnector": "outs",
      "fromSubConnector": 1,
      "toOperator": 40,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #d81b60"
    },
    "2": {
      "fromOperator": "0",
      "fromConnector": "outs",
      "fromSubConnector": 2,
      "toOperator": 41,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #8e24aa"
    },
    "3": {
      "fromOperator": "0",
      "fromConnector": "outs",
      "fromSubConnector": 3,
      "toOperator": 43,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #5e35b1"
    },
    "4": {
      "fromOperator": 37,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": 38,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #3949ab"
    },
    "5": {
      "fromOperator": 40,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": 42,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #546e7a"
    },
    "6": {
      "fromOperator": 41,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": 42,
      "toConnector": "ins",
      "toSubConnector": 1,
      "color": " #039be5"
    },
    "7": {
      "fromOperator": 38,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": 39,
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #00acc1"
    },
    "8": {
      "fromOperator": 42,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": 39,
      "toConnector": "ins",
      "toSubConnector": 1,
      "color": " #00897b"
    },
    "9": {
      "fromOperator": 39,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": "1",
      "toConnector": "ins",
      "toSubConnector": 0,
      "color": " #43a047"
    },
    "10": {
      "fromOperator": 43,
      "fromConnector": "output",
      "fromSubConnector": 0,
      "toOperator": "1",
      "toConnector": "ins",
      "toSubConnector": 1,
      "color": " #7cb342"
    }
  },
  "operatorTypes": {}
}

I want to use this for filtering data in tabulator so turn this into this:

[
  {
    "id": 1,
    "pid": null
  },
  {
    "id": 39,
    "pid": 1
  },
  {
    "id": 38,
    "pid": 39
  },
  {
    "id": 37,
    "pid": 38
  },
  {
    "id": 42,
    "pid": 39
  },
  {
    "id": 40,
    "pid": 42
  },
  {
    "id": 41,
    "pid": 42
  },
  {
    "id": 43,
    "pid": 1
  }
]

Then using this lines I turned into a tree:

const idMapping = data.reduce((acc, el, i) => {
  acc[el.id] = i;
  return acc;
}, {});

let root;
data.forEach(el => {
  // Handle the root element
  if (el.pid === null) {
    root = el;
    return;
  }
  // Use our mapping to locate the parent element in our data array
  const parentEl = data[idMapping[el.pid]];
  // Add our current el to its parent's `children` array
  parentEl.children = [...(parentEl.children || []), el];
});

Here it is as a tree:

{
  "id": 1,
  "pid": null,
  "children": [
    {
      "id": 39,
      "pid": 1,
      "children": [
        {
          "id": 38,
          "pid": 39,
          "children": [
            {
              "id": 37,
              "pid": 38
            }
          ]
        },
        {
          "id": 42,
          "pid": 39,
          "children": [
            {
              "id": 40,
              "pid": 42
            },
            {
              "id": 41,
              "pid": 42
            }
          ]
        }
      ]
    },
    {
      "id": 43,
      "pid": 1
    }
  ]
}

Hovewer I want to simplify this further to just nested arrays of the id's. Like this:

[
   1,
   [
      [
         39,
         [
            [
               38,
               [
                  37
               ],
            ],
            [
               42,
               [
                  40,
                  41
               ]
            ]
         ]
      ],
      43
   ]
]

Question is, how to convert from tree to nested array?


Solution

  • You can use recursion to do this; leaf ids are passed upward without an array wrapper while interior node ids are wrapped in an array with a second element for their children.

    const tree = { "id": 1, "pid": null, "children": [ { "id": 39, "pid": 1, "children": [ { "id": 38, "pid": 39, "children": [ { "id": 37, "pid": 38 } ] }, { "id": 42, "pid": 39, "children": [ { "id": 40, "pid": 42 }, { "id": 41, "pid": 42 } ] } ] }, { "id": 43, "pid": 1 } ] };
    
    const objTreeToArrTree = node => 
      node.children 
        ? [node.id].concat([node.children.map(objTreeToArrTree)])
        : node.id
    ;
    console.log(JSON.stringify(objTreeToArrTree(tree), null, 2));