[SOLVED] How to draw a collapsible tree diagram using D3JS in Drupal 7 with Javascript?

| | 5 min read

In one of my project, I want to display a hierarchical structure of an array by using D3 JS, which should be collapsible. Lets checkout how we can implement, D3 JS in Drupal 7 with collapsible concept. Do you want the implementation like this, get a quote and make use of our expertise.

First I download the D3 JS from http://d3js.org/ and save it in [my-js] folder (sites/all/libraries/my-js) as 'd3.js'. So I use the following code for this tree diagram :

  • First I have created 2 menus by implementing hook_menu(), first one for the page details and the other for ajax request:
    // Menu1.
      $items['data_hierarchy'] = array(
        'title' => 'Page title',
        'description' => 'Handling the structure',
        'page callback' => array('data_hierarchy'),
        'file' => 'includes/data_hierarchy.inc',
        'access arguments' => array('PERMISSION DETAILS'),
      );
      // Menu2.
      $items['ajax/data_hierarchy'] = array(
        'title' => '',
        'description' => 'Handling ajax request of structure',
        'page callback' => array('data_hierarchy_ajax'),
        'file' => 'includes/data_hierarchy.inc',
        'access arguments' => array('PERMISSION DETAILS'),
      );
  • I have implemented hook_theme() for its TPL page,
    // theme for structure.
      $theme['data_hierarchy'] = array(
        'variables' => array('vars' => NULL),
        'template' => 'templates/data_hierarchy',
      );
  • Then I have created its pagecallbacks,
    /**
     * Pagecallback for structure.
     *  @return $output.
     */
    function data_hierarchy() {
      $output_data = array(
        '#theme' => 'data_hierarchy',
        '#vars' => '',
        '#attached' => array(
          'js' => array(
            drupal_add_js(libraries_get_path('LIBRARY NAME') . '/--PATH--/d3.js', 'external'),
            drupal_get_path('module', 'MODULE NAME') . '/js/data_hierarchy.js',
          ),
          'css' => array(
            drupal_get_path('module', 'MODULE NAME') . '/css/data_hierarchy.css',
          ),
        )
      );
      return $output_data;
    }
    
    /**
     * Pagecallback for handling ajax request of structure.
     *  @return json_output.
     */
    function data_hierarchy_ajax() {
      $sample_array_data =  array(
        array("name" => "Management",
          "parent" => "null",
          "children" => array(
            array(
              "name" => "Dept/Unit1",
              "parent" => "Management",
              "children" => array(
                array(
                  "name" => "Group",
                  "parent" => "Dept/Unit1",
                  "children" => array(
                    array(
                      "name" => "Group1",
                      "parent" => "Group",
                      "children" => array(
                        array(
                          "name" => "Employee1",
                          "parent" => "Group1",
                        ),
                        array(
                          "name" => "Employee2",
                          "parent" => "Group1",
                        ),
                        array(
                          "name" => "Employee..n",
                          "parent" => "Group1",
                        ),
                      )
                    ),
                    array(
                      "name" => "Group2",
                      "parent" => "Group",
                    ),
                    array(
                      "name" => "Group..n",
                      "parent" => "Group",
                    ),
                  )
                ),
                array(
                  "name" => "Team",
                  "parent" => "Dept/Unit1",
                  "children" => array(
                    array(
                      "name" => "Team1",
                      "parent" => "Team",
                      "children" => array(
                        array(
                          "name" => "Employee1",
                          "parent" => "Team1",
                        ),
                        array(
                          "name" => "Employee2",
                          "parent" => "Team1",
                        ),
                        array(
                          "name" => "Employee..n",
                          "parent" => "Team1",
                        ),
                      )
                    ),
                    array(
                      "name" => "Team2",
                      "parent" => "Team",
                    ),
                    array(
                      "name" => "Team..n",
                      "parent" => "Team",
                    ),
                  )
                )
              )
            ),
            array(
              "name" => "Dept/Unit2",
              "parent" => "Management"
            ),
            array(
              "name" => "Dept/Unit..n",
              "parent" => "Management"
            )
          )
        )
      );
    
      drupal_json_output($sample_array_data);
    }
  • I have stored the following css for the structure in 'MODULE_NAME/css/data_hierarchy.css',
    .node {
      cursor: pointer;
    }
    
    .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 3px;
    }
    
    .node text {
      font: 12px sans-serif;
    }
    
    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 2px;
    }
    
    #data-hierarchy {
      background-color: #FFFFFF;
      overflow: auto;
    }
    
    #data-hierarchy h4{
      padding-left: 1%;
    }
  • I have stored the following Javascript code in the file 'MODULE_NAME/js/data_hierarchy.js' for reading the JSON data and drawing the tree like structure:
    (function ($) {
      Drupal.behaviors.my_data_hierarchy = {
        attach:function(context, settings) {
          var base_url = window.location.origin;
          var treeData = [];
          var margin = {top: 20, right: 120, bottom: 20, left: 120},
            width = 960 - margin.right - margin.left,
            height = 500 - margin.top - margin.bottom;
    
          var i = 0,
            duration = 750,
            root;
    
          var tree = d3.layout.tree()
            .size([height, width]);
    
          var diagonal = d3.svg.diagonal()
            .projection(function(d) { return [d.y, d.x]; });
          var svg = d3.select("#data-hierarchy").append("svg")
            .attr("width", width + margin.right + margin.left)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
          // Load json data for generating the structured display.
          jQuery.getJSON(base_url+'/ajax/data-hierarchy/', function(data) {
            if (data != '') {
              // Generate the tree diagram.
              treeData = data;
              root = treeData[0];
              root.x0 = height / 2;
              root.y0 = 0;
              root.children.forEach(collapse);
              update(root);
              d3.select('#data-hierarchy').style("height", "500px");
            }
            else {
              $('#data-hierarchy').html("<h4>No hierarchy details found.</h4>");
            }
          });
    
          function collapse(d) {
            if (d.children) {
              d._children = d.children;
              d._children.forEach(collapse);
              d.children = null;
            }
          }
    
          function update(source) {
            // Compute the new tree layout.
            var nodes = tree.nodes(root).reverse(),
              links = tree.links(nodes);
    
            // Normalize for fixed-depth.
            nodes.forEach(function(d) { d.y = d.depth * 180; });
    
            // Update the nodes…
            var node = svg.selectAll("g.node")
              .data(nodes, function(d) { return d.id || (d.id = ++i); });
    
            // Enter any new nodes at the parent's previous position.
            var nodeEnter = node.enter().append("g")
              .attr("class", "node")
              .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
              .on("click", click);
    
            nodeEnter.append("circle")
              .attr("r", 1e-6)
              .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
    
            nodeEnter.append("text")
              .attr("x", function(d) { return d.children || d._children ? -13 : 13; })
              .attr("dy", ".35em")
              .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
              .text(function(d) { return d.name; })
              .style("fill-opacity", 1e-6);
    
            // Transition nodes to their new position.
            var nodeUpdate = node.transition()
              .duration(duration)
              .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
    
            nodeUpdate.select("circle")
              .attr("r", 10)
              .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
    
            nodeUpdate.select("text")
              .style("fill-opacity", 1);
    
            // Transition exiting nodes to the parent's new position.
            var nodeExit = node.exit().transition()
              .duration(duration)
              .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
              .remove();
    
            nodeExit.select("circle")
              .attr("r", 1e-6);
    
            nodeExit.select("text")
              .style("fill-opacity", 1e-6);
    
            // Update the links…
            var link = svg.selectAll("path.link")
              .data(links, function(d) { return d.target.id; });
    
            // Enter any new links at the parent's previous position.
            link.enter().insert("path", "g")
              .attr("class", "link")
              .attr("d", function(d) {
              var o = {x: source.x0, y: source.y0};
              return diagonal({source: o, target: o});
              });
    
            // Transition links to their new position.
            link.transition()
              .duration(duration)
              .attr("d", diagonal);
    
            // Transition exiting nodes to the parent's new position.
            link.exit().transition()
              .duration(duration)
              .attr("d", function(d) {
              var o = {x: source.x, y: source.y};
              return diagonal({source: o, target: o});
              })
              .remove();
    
            // Stash the old positions for transition.
            nodes.forEach(function(d) {
            d.x0 = d.x;
            d.y0 = d.y;
            });
          }
    
          // Toggle children on click.
          function click(d) {
            if (d.children) {
            d._children = d.children;
            d.children = null;
            } else {
            d.children = d._children;
            d._children = null;
            }
            update(d);
          }
        }
      }
    })(jQuery);
  • Then I have created the TPL page with the code :
    <div id="data-hierarchy"></div>

Do you need any technical support, feel free to contact us. Happy coding :)