Search code examples
javascriptjsond3.jsnested-listscircle-pack

D3 - accessing nested data for the purpose of creating a navigation tree


So I have been using d3's zoom circle(http://mbostock.github.com/d3/talk/20111116/pack-hierarchy.html):

enter image description here

The data is passed to the d3 script as a json array with a few different levels of arrays and objects.

object = {
    class: "show",
    name: "Game of Thrones",
    children : [
        {
            class: "family",
            name: "Starks",
            children: [
                {
                    class: "members",
                    name: "Rob"
                },
                {
                    class: "members",
                    name: "Jon Snow"
                }
            ],
        },
        {
            class: "family",
            name: "Lannisters"
            children: [
                {
                    class: "members",
                    name: "Ser Jaime"
                },
                {
                    class: "members",
                    name: "Cersei"
                }
            ],
        }            
    ],
}

I had no problem displaying the circles. I am now trying to create a navigation display on the side that maps out the hierarchy of the data. Ideally all I want is something like this:

<ul>
  <li> Game of Thrones
    <ul>
      <li> Starks
        <ul>
          <li> Rob </li>
          <li> Jon Snow </li>
        </ul>
      </li>
      <li> Lannisters
        <ul> 
          <li> Ser Jaime </li>
          <li> Cersei </li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

It's a basic list structure. The only thing I've been able to do is access 1 level down in the data. I used 'divs' to just try to the the structure down first.

var sentimentData = data.children;

var navBox = d3.select("body").append("body:div");

navBox
    .attr("class", "navBox");

var sentimentNav = navBox.selectAll('div')
    .data(sentimentData)
    .enter()
    .append('div')
    .text(function(d){ return d.name; });

I haven't been able to proceed anymore levels past that. I was thinking a recursive way would probably be the best way. I tried this function below but it just appended divs to the top div and not the parent node.

function buildNav(d) {
 if (d.children) {
   children = d.children;
   d3.select('body').append('div')
      .data(children)
      .enter()
      .append('div')
      .attr("class", function(d) {return d.name; });
   children.forEach(buildNav); 
   d._children = d.children;  
   d.children = null;
 }
}

buildNav(data);

Any suggestions to how to append the children to their parents, or how to access data multiple levels down would be greatly appreciated.


Solution

  • Nick! Here is the complete code of an example that creates lists that you described in the question: (there might be a nicer code to do the same thing, but this code works and produces 100% what you said) (if course, this is only a starting point, you will naturally develop further real navigation)

    index.html:

    <!DOCTYPE html>
    <html>
    <head>
        <title>Navigation</title>
        <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
        <script src="script.js"></script>
    </head>
    <body onload="load()">
        <h2>Navigation</h2>
        <h4>(using D3.js-generated navigation items)</h4>
    </body>
    </html>
    

    script.js:

    function load(){
        object = {
            class: "show", name: "Game of Thrones",
            children : [
                {
                    class: "family", name: "Starks",
                    children: [
                        { class: "members", name: "Rob" },
                        { class: "members", name: "Jon Snow" }
                    ],
                },
                {
                    class: "family", name: "Lannisters",
                    children: [
                        { class: "members", name: "Ser Jaime"},
                        { class: "members", name: "Cersei" }
                    ],
                }            
            ],
        };
    
        buildNav(null, object);
    
        function buildNav(parent_ul, node){
            var current_ul, current_li;
            if(parent_ul == null)
                parent_ul = d3.select("body").append("ul");
            current_li = parent_ul.append("li").text(node.name);
            if (node.children) {
                current_ul = current_li.append("ul");  
                for (var i=0; i<node.children.length; i++) {
                    buildNav(current_ul, node.children[i]); 
                };
            };
        };
    };
    

    This produces following page:

    enter image description here

    Hope this would help you.

    Let me know if you have additional questions.