Directed Acyclic Hierarchical Graph Implementation - javascript

I need to display a acyclic directed graph looking somewhat like this:
I created a nested hierarchical Data Structure similar to this:
[
{
node: 'abs'
children: [
{
node: 'jhg',
children: [{...}]
{
node: 'AAA',
children: [{...}]
},
{
node: 'fer'
children: [
{
node: 'AAA',
children: [{...}]
{
node: 'xcv',
children: [{...}]
},
{
]
I am not sure if this is the best way to actually display the data since the nodes with multiple parents and their children would appear multiple times, but I had no other idea how to handle it yet.
I simply want to render those nodes to an imaginary grid. Therefore i need to parse my data structure and set their grid values. The problem is i dont know how to parse the data structure with hierarchy logic.
What I am doing right now is obviously causing problems for nodes with multiple parents:
for (const root of allRoots) {
currentLevel = 0;
if (root.node === 'VB8') {
getChildrenTree(root);
}
}
function getChildrenTree(node) {
currentLevel++;
node._gridX = currentLevel;
if (node.children.length > 0) {
for(const nextChild of children ) {
getChildrenTree(nextChild);
}
}
The Problem with this code is that it will only run through one path and then stop when there arent any children.
I just need an algorithm which runs through the tree and sets each nodes hierarchy level.
I hope that this is not too confusing.

If you want to reference the same node from two separate parents, you shouldn't define it more than once. I suggest listing all nodes in a flat array with a single 'invisible' root node and referencing children by id or array index:
[
{id: 0, name: "root", children: [1, 2]},
{id: 1, name: "abs", children: [3, 4]},
{id: 2, name: "fer", children: [5, 6]},
{id: 3, name: "jhg", children: [...]},
{id: 4, name: "AAA", children: [...]},
...
]
then you can recursively set their tree depths like so:
function setDepth(node, depth) {
if (node._gridX && node._gridX >= depth) {
// node has been visited already through a path of greater or equal length
// so tree depths wouldn't change
return
}
node._gridX = depth
node.children
.map(idx => nodeArray[idx]) // get the actual objects from indices
.forEach(child => setDepth(child, depth+1))
}
setDepth(nodeArray[0], 0) // start at root
... careful though, as this algorithm will get stuck in a loop if your nodes have any cycles

Related

Convert array to tree

There is an array of data that needs to be converted to a tree:
const array = [{
id: 5,
name: 'vueJS',
parentId: [3]
}, {
id: 6,
name: 'reactJS',
parentId: [3]
}, {
id: 3,
name: 'js',
parentId: [1]
}, {
id: 1,
name: 'development',
parentId: null
}, {
id: 4,
name: 'oracle',
parentId: [1,2]
}, {
id: 2,
name: 'data-analysis',
parentId: null
}];
Now it works using this function:
function arrayToTree(array, parent) {
var unflattenArray = [];
array.forEach(function(item) {
if(item.parentId === parent) {
var children = arrayToTree(array, item.id);
if(children.length) {
item.children = children
}
unflattenArray.push(item)
}
});
return unflattenArray;
}
console.log(arrayToTree(array, null));
I have two problems with this feature:
The value of "parentId" should be an array of id, for example -
"parentId": [2, 3]
How to transfer to function only one argument - "array"?
https://codepen.io/pershay/pen/PgVJOO?editors=0010
I find this question confusing. It sounds like what you are really saying is the array represents the “definition of node types in the tree” and not the actual instances of those nodes that will be in the tree.
So your problem is you need to copy the “definitions” from the array to new “instance” nodes in your tree. This would let “Oracle” show twice, as you’d create a new “oracle instance” node for each parent in its parent array. It wouldn’t technically need to be a deep copy depending on your use, so you could proof of concept with Object.assign, but each instance would point to the same parents array and that may or may not cause problems for that or future reference values you add to the definition.
Finally, depending on the size of the tree and what you are really trying to do, you might want to convert to a tree represented by nodes/edges instead of parent/children. For really large datasets recursion can sometimes cause you problems.
Sorry I’m on my phone so some things are hard to see on the codepen.

Identify circular dependency in a Json object and remove all element after 2 depth

I have a json object something like this:
var temp1 = {
name: "AMC",
children: [
{
name: "cde",
children: [
{
name: "AMC",
children: [
{
name: "cde",
children: [
{
name: "AMC",
children: [
//.............. continues as curcular depndency
]
}
]
}
]
}
]
},
{
name: "mnp",
children: [
{
name: "xyz",
children: []
}
]
}
]
}
Due to this cicular dependency, JSON.stringify is failing.
I have done enough google and searching to get the solution for this but could not find much help.
So here basically I want to detect a circular dependency in the json object and add a new key to the object, saying cricular: true and remove all the subsequent node.
So here is the result output what I am looking :
var temp1 = {
name: "AMC",
children: [
{
name: "cde",
circular: true,
children: [ // No children here as it is curcular dependency
]
},
{
name: "mnp",
children: [
{
name: "xyz",
children: []
}
]
}
]
}
There is a way, which I think can solve it, where I can loop through all the children unless there is no children upto maximum 2 levels, but that way I will miss valid children which are having depth more than 3.
I hope my question is clear. If not please let me know I will try to expand this further.
A recursive function solves this:
function check(stack,parent, obj){
stack = stack || []; //stack contains a list of all previously occurred names
var found = stack.find(function(parent){
return (parent==obj.name && obj.children.length>0); //checks to see if the current object name matches any in the stack.
});
if(!found && obj.children.length>0){
stack.push(obj.name); //adds the current object name to the list.
obj.children.forEach(function(child){
check(stack,obj, child);//recursively checks for all children.
})
}
else if(found){
parent.children=[];
parent.circular=true;
stack.pop(obj.name);
return;
}
else{
return;
}
}
check([],temp1, temp1)
This leads to alteration of the original object passed.
Hope this helps!
use console.table(circularObj) to help you in debugging

Loop to display hierarchical data

I am creating an array out of essentially hierachical data, for example as below:
[
{id: 1, title: 'hello', parent: 0, children: [
{id: 3, title: 'hello', parent: 1, children: [
{id: 4, title: 'hello', parent: 3, children: [
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4}
]},
{id: 7, title: 'hello', parent: 3}
]}
]},
{id: 2, title: 'hello', parent: 0, children: [
{id: 8, title: 'hello', parent: 2}
]}
]
I am looking to loop through the array, but can't get my head around how to recursively loop down to create an unordered list where each child level is indented.
Trying to do this in JavaScript, but need a push in the right direction for the construction of the loop to drill down until there are no more children, and then back up to the top array.
Any help would be appreciated.
I answered a question about this before
Here is demo for it: http://jsfiddle.net/zn2C7/7/
var list = $("<ul>");
function populatedata() {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == -1) {
var item = $("<li>").html(folder.FolderName);
list.append(item);
var children = $('<ul>');
item.append(children);
checkChild(folder.FolderID, children);
}
});
$('body').append(list);
}
function checkChild(parentid, parent) {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == parentid) {
var item = $("<li>").html(folder.FolderName);
var children = $('<ul>');
parent.append(item);
item.append(children);
checkChild(folder.FolderID, children);
}
else {
return ;
}
});
}
It was possible to build it using html variable, like you tried to do that, but it is much simpler to use DOM manipulation functions of jQuery ($('<ul>') and $('<li>') - create new element, .append() - append element to some other element)
function checkChild(parentid) {
$.each(data.FolderList, function (i, folder) {
if (folder.ParentFolderID == parentid) {
html += '<li><ul>' + folder.FolderName;
checkChild(folder.FolderID);
html+=</ul></li>
return html;
}
else {
return ;
}
});
}
Also, please note that in code above you are doing return html; from each function callback. Not sure what you wanted to get exactly, but in .each it may work like break in regular loop (if you will return false):
We can stop the loop from within the callback function by returning false.
That is from jquery api page.
Also, for such tasks I prefer to use debugger. At this moment there are a lot of powerful tools for HTML/CSS/JS debugging in browser. Just press F12 in Chrome, IE or FF (for the last one you may need to install Firebug extension) and you will get a lot of helpful features along with simple JS debugging.

Creating an Object from an Array of Objects with references to parent Object

Sorry for the confusing title... I don't know a better summery.
I have an Array of Objects. Some of these Objects have a reference to it's parent Object. Something like that:
data:
[
{id: 2, parent: 1},
{id: 1},
{id: 3, parent: 1},
{id: 5, parent: 3},
{id: 4, parent: 3},
{id: 6, parent: 2}
]
What I want to do, is creating an Object out of this Array where the child objects are nested inside their parents. Like that:
data: {
id: 1,
children: [
{
id:2,
children: [
{id: 6}
]
},
{
id:3,
children: [
{id: 4},
{id: 5}
]
}
]
}
Does anyone know a smart way of doing this?
I know I have to itterate through every Object of this Array and check if there is a parent. But how can I actually create this Object?
Not sure it's the best way to do it, but at least it's one way to do it.
First loop over the nodes:
Put the nodes in a lookup table by their id (nodes).
Find the root node (the single node with no parent).
Second loop (with the lookup table complete):
Check if the node has a parent (true for every node except the root).
Get the parent node by looking up the id in the lookup table.
Get the parent.children array, or create it if it doesn't exist yet.
Add this node to that array.
Remove the parent property of this node.
Note that this changes the original node objects in your data object. This is intentional, since the tree is built by storing references to other nodes in parent nodes. If you need to keep the original nodes in data intact, you should clone the node objects while building the lookup table. For example, you could add node = $.extend({}, node); as first line in the lookup forEach loop (when using jQuery).
Here's an implementation and a demonstration:
var data = [
{id: 2, parent: 1},
{id: 1},
{id: 3, parent: 1},
{id: 5, parent: 3},
{id: 4, parent: 3},
{id: 6, parent: 2}
];
// Node lookup table
var nodes = {};
// Root node
var root = null;
// Fill lookup table and find root
data.forEach(function(node) {
nodes[node.id] = node;
// Assuming only one root node
if(!("parent" in node)) {
root = node;
}
});
// Build tree
for(var id in nodes) {
var node = nodes[id];
if("parent" in node) {
// Add to children of parent
var parent = nodes[node.parent];
(parent.children = parent.children || []).push(node);
// Remove parent property (optional)
delete node.parent;
}
}
console.log(JSON.stringify(root));

D3: use nest function to turn flat data with parent key into a hierarchy

I'm sure there's a really simple elegant way to do this but I can't quite figure it out. I have some input data that looks like this:
[
{id: 1, name: "Peter"},
{id: 2, name: "Paul", manager: 1},
{id: 3, name: "Mary", manager: 1},
{id: 4, name: "John", manager: 2},
{id: 5, name: "Jane", manager: 2}
]
If possible, I would like to use the d3.js nest operator to get a structure to use in the hierarchy layout. Like this:
[
{name: "Peter", children: [
{name:"Paul", children: [
{name:"John"},
{name:"Jane"}
]},
{name:"Mary"}
]
}
]
You can't use the nest operator here because nesting produces a fixed hierarchy: the number of levels in the output hierarchy is the same as the number of key functions you specify.
That said, you can write your own function which produces a tree. Assuming that the root node is the first node in the input array, you can create a map from id to node, and then construct the tree lazily.
function tree(nodes) {
var nodeById = {};
// Index the nodes by id, in case they come out of order.
nodes.forEach(function(d) {
nodeById[d.id] = d;
});
// Lazily compute children.
nodes.forEach(function(d) {
if ("manager" in d) {
var manager = nodeById[d.manager];
if (manager.children) manager.children.push(d);
else manager.children = [d];
}
});
return nodes[0];
}
If you know that the nodes are listed in order such that managers appear before their reports, you can simplify the code to iterate only once.

Categories

Resources