Question

So I've got some JSON like this (keys/values changed for privacy reasons). It's basically an Object with a "children" array of Objects, with each element having its own "children" array of Objects, et cetera.

{
"name": "Main Area",
"children": [
    {
        "name": "Sub-section A",
        "children": [
            {
                "name": "Procedure 1"
                "children": [
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    },
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    },
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    }
                ]
            },
            {
                "name": "Procedure 2"
                "children": [
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    },
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    },
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    }
                ]
            },
            {
                "name": "Procedure 3"
                "children": [
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    },
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    },
                    {
                        "name": "Sub-procedure A",
                        "children": null
                    }
                ]
            },
        ]
    }
]
}

... and it's possible that that gets several times larger/deeper as I go along. The goal here is to transform this data into a (collapsable) tree diagram and then showing it on a HTML page using knockout.js (preferably). All the logic has to be JavaScript code though (grab JSON with jQuery AJAX, treat it in some way, show it).

Like this only then rotated 90 degrees so it goes down and not to the side. http://www.education.vic.gov.au/images/content/studentlearning/mathscontinuum/icecream.gif

Now I know that there are libraries like jsTree out there, but the point is that I want this to be a learning experience as well so I want to create my own code here. Also, jsTree prints its structures like a directory tree. The thing I'm looking for is a more visual top-down tree diagram that fills the whole width of the page.

Thing is, I'm stuck. I just can't wrap my head around all these nested arrays/objects. I've probably spent about 3 hours trying things, creating several different functions to iterate recursively through the entire array but I can't get it to work right.

The latest idea that crossed my mind was to somehow go through the JSON structure, starting from the deepest element, moving upwards, somehow translating the steps into tree diagram levels, until I reach the top, at which point it's finished.

So, willing to point me in the right direction? You don't have to write dozens of lines of code for me, just give me an indication, some pseudocode perhaps, of where I should take this.

Thanks in advance!

UPDATE

I've come a bit futher. Just thought I'd share my code for future reference. I now have a populated array of node objects and a basic ul/li structure (can still throw knockout.js at it later). Still have to figure out the CSS/JS part but that'll come as I go along.

Something worth noting is that I changed every "null" value in the JSON structure to "[]", this is more convenient to use in this context.

//node class
function node(label, children, node_id, parent_id) {
    this.label = label;
    this.children = children;
    this.node_id = node_id;
    this.parent_id = parent_id;
}

//tree class
function tree(root) {
    var self = this;
    self.root = root;
    if(!$.isArray(self.root.children)) {
        return;
    }
    self.nodeCount = 0;
    self.nodes = []; //This will be the main node array

    //Adds nodes to the array, recursive
    self.addNode = function(_node) {
        self.nodeCount++;
        self.nodes.push(_node);
        //Check if the branch ends here
        if(_node.children.length < 1) {
            return;
        }
        var tmpParent = _node;
        //Add children
        for(var i = 0; i < _node.children.length; i++) {
            self.addNode(new node(_node.children[i].name, _node.children[i].children, self.nodeCount, tmpParent.node_id));
        }
    }

    self.draw = function() {
        //Populate nodes array
        self.rootNode = new node(self.root.name, self.root.children, 0, -1);
        self.addNode(self.rootNode); //Kicks off the recursion
        //Create the first/"root" node
        $(".tree-wrapper").append('<ul id="0"><li class="title">'+ self.rootNode.label +'</li></ul>');
        //Create nodes and populate them with children
        for(var i = 0; i < self.nodes.length; i++) {
            //Check if the node has children
            if(self.nodes[i].children.length > 0) {
                //Wrap a new <ul> in a <li>, add a title <li> to the created <ul>
                $("#"+ self.nodes[i].parent_id).append('<li><ul id="'+ self.nodes[i].node_id +'"><li class="title">'+ self.nodes[i].label +'</li></ul></li>');
            } else {
                //Simply append a <li> with a label to the parent <ul>
                $("#"+ self.nodes[i].parent_id).append('<li id="'+ self.nodes[i].node_id +'">'+ self.nodes[i].label +'</li>');
            }
        }
    }
}

Thank you to everyone who answered! If you have hints/tips don't hesitate ;) My code can probably be shortened/optimized in some way!

Was it helpful?

Solution

I've recently implemented the exact same thing, with knockout, but rendered with DOM elements, not a canvas.

How I did it:

<script ko template = "node">
    <div class="node">
        <a href="#" data-bind="click: toggleChildren()">Open children</a>
        <div class="children" data-bind="visible: childrenOpen, foreach: children, template: node"> 
        </div>   
    </div>
</script ko template>

<div class="nodes" data-bind="foreach: children, template: node">
</div>

On the ViewModel side:

function MyVM() {
    this.nodes = ko.observable([]); // this is a list of Node objects. each Node object has a list of children, that are also Node objects
}

ko.applyBindings(new MyVM());

On the Model side:

function Node(name, etc.) {
    var self = this;

    this.childrenOpen = ko.observable(false);
    this.children = ko.observable([]);

    this.toggleChildren = function() {
        self.childrenOpen(!self.childrenOpen());
    }
}

All that's left to do is: 1) populate nodes on the ViewModel; 2) write CSS so that it looks like a tree.

I actually used nested unordered lists (< ul >< li >) but that should not matter.

Now, this is just displaying. If you need interactivity you'll probably need to implement a couple of tree walking algorithms and keep references to parent nodes in children (this helps a lot).

OTHER TIPS

Assuming you are ie8+ you can use the JSON object. This code is untested but the main idea is that you are using recursion and passing in the bounds which the element is allowed to print in. That way each element can print itself without knowing about it's siblings or grand-children. Also, this code assumes you have room to print everything and a magic function drawLine.

function printLevel(elem, start, end, level) {
    var width = end - start;  // Width this elem gets for printing
    var mid = start + mid / 2;  // midpoint for printing this elem's name
    var myName = document.createElement("p");
    myName.appendChild(document.createTextNode(elem.name));
    myName.x = mid - fontSize * elem.name.length / 2;  // fontSize in pixels
    myName.y = level * 20; 
    document.body.appendChild(myName);
    var children = elem.children;
    if (children !== null) {
        var childWidth = width / children.length;  // Each child gets a portion of elem's width
        for (var i = 0; i < children.length; i++) {
            printLevel(children[i], start + i * childWidth, end + (i + 1) * childWidth, level + 1);
            drawLine(elem, children[i]);
        }
    }
 }
 var tree = JSON.parse(response.text);
 printLevel(tree, 0, screen.innerWidth, 0);

Sorry, I don't know much in other language but PHP. In PHP I will use

// first degree
foreach ($result->children as $arr) { 
// Where $result is data i got from json_decode(fetch_data())
  echo $arr->name; // Sub-section A

     // second degree
     foreach ($arr->children as $arr2) {
        echo $arr2->name; //Procedure 1, Procedure 2 , ...
     }
}

Hope you get my point and apply to your java.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top