Blog

Descendancy With D3

Feb 4, 2019 | 8 minutes read

index.html

<div>
  <style>

  .tree{
      width: 100%;
      height: 100%;
  }
  .node {
      cursor: pointer;
  }
  .node circle {
      fill: #888;
  }
  .node text {
      fill: #666;
      font-size: 14px;
  }
  .node path {
      fill: #fff;
      stroke: #fff;
      stroke-width: 0.2px;
  }

  .node0 circle {
      fill: #aaceff;
      stroke-opacity: 0.5;
      stroke: #aaceff;
      stroke-width: 8px;
  }
  .node1 circle {
      fill: #fd9bb4;
      stroke: #fd9bb4;
      stroke-width: 6px;
  }
  .node2 circle {
      fill: #af9ffc;
      stroke: #af9ffc;
      stroke-width: 6px;
  }
  .node3 circle {
      fill: #9be4b7;
  }
  /*.node.node-child circle {
      stroke-opacity: 0.5;
      transform: scale(1.2);
  }*/

  .node:hover text {
      fill: #333;
      font-weight: bold;
      font-size: 14px;
  }
  .node:hover circle {
      transform: scale(1.2);
  }
  .node:not(.node3) text {
      text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
  }
  .node0 text{
      font-size: 16px!important;
  }
  .link {
      fill: none;
      stroke: #c6c6c6;
      stroke-opacity: 0.4;
      stroke-width: 1px;
  }

  body {
    background-color: white
  }

  </style>
<script src="https://d3js.org/d3.v4.min.js"></script>

<div id="tree" class="tree"></div>
<script>

var tree, // d3
    rootData, //
    container, // svg
    linkContainer, // link
    nodeIndex = 0; // id

draw();

function draw() {

    function getStyle(oElement, sName) { // IE
        return oElement.currentStyle ? oElement.currentStyle[sName] : window.getComputedStyle(oElement, null)[sName];
    }

    tree = d3.tree()
        .size([Math.PI * 2, 600])
        .separation(function(a, b) { return (a.parent === b.parent ? 1 : 2) / a.depth });

    var con = d3.select("#tree"),
        width = parseInt(getStyle(con._groups[0][0], "width"), 10),
        height = parseInt(getStyle(con._groups[0][0], "height"), 10),
        svg = con.append("svg")
            .attr("xmlns", "http://www.w3.org/2000/svg")
            .attr("width", width)
            .attr("height", height);

    container = svg.append("g");
    linkContainer = container.append("g");

    // chrome
    var zoom = d3.zoom()
        .scaleExtent([0.4, 2])
        .on("zoom", function() {
            // v4: translate scale event.transform
            // d3.event.transform
            // "translate(" + d3.event.transform.x + "," + d3.event.transform.y ")scale(" + d3.event.transform.k + ")"
            container.attr("transform", d3.event.transform);
        });
    svg.call(zoom);
    //
    svg.call(zoom.transform, d3.zoomIdentity.translate(width / 2, height / 2).scale(0.9));

    getData();
}

function getData() {
    d3.json("https://gist.githubusercontent.com/rykermorgan/bb3b38c5f3b0a8ac9b11d31eee2e3689/raw/18d78005069b3fef729f553ae122b63beefc1f0e/jm_family.json", function(error, data) {
        if (error) throw error;

        // d3
        rootData = d3.hierarchy(data, function(d) { return d.children; });
        rootData.children.forEach(collapse);

        // Collapse the node and all it's children
        function collapse(d) {
            if(d.children) {
                d._children = d.children;
                d._children.forEach(collapse);
                d.children = null;
            }
        }

        drawTree(rootData);
    });
}

//
function drawTree(source) {

    /*
     * @method radialPoint
     * @param {Number} x
     * @param {Number} y
     * @return {Array}
     */
    function radialPoint(x, y) {
        return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
    }

    //
    source.x0 = 3.141592653589793;
    source.y0 = 0;

    var treeData = tree(rootData); // Assigns the x and y position for the nodes

    var nodes = treeData.descendants(); // Array
    var links = treeData.links(); // Array

    // Normalize for fixed-depth
    nodes.forEach(function(d) {
        if(d.depth === 1) {
            d.y = d.depth * 180;
        }else {
            d.y = d.depth * 220;
        }
    });

    var node = container.selectAll(".node")
        .data(nodes, function(d) { // id
            return d.id || (d.id = ++nodeIndex);
        });

    // Enter
    var nodeEnter = node.enter().append("g")
        .attr("class", function(d) {
            return "node node" + d.depth;
        })
        .attr("transform", function(d) {
            return "translate(" + radialPoint(source.x0, source.y0) + ")";
        })
        .on("click", function(d) {
            if(d.depth > 0) toggle(d);
        });

    nodeEnter.append("circle")
        .attr("r", 1e-6)
        .style("stroke-opacity", function(d) {
            if(d.children) {
                return 0.5;
            }else {
                return 0;
            }
        });

    nodeEnter.append("path")
        .attr("d", function(d) {
            if(d.depth > 0 && d._children) {
                return "M-6 -1 H-1 V-6 H1 V-1 H6 V1 H1 V6 H-1 V1 H-6 Z";
            }else if(d.depth > 0 && d.children) {
                return "M-6 -1 H6 V1 H-6 Z";
            }
        })
        .style("fill-opacity", 0);

    nodeEnter.append("text")
        .attr("dy", function(d) {
            if(d.depth === 0) return "-1.5em";
            return "0.31em";
        })
        .attr("x", function(d) {
            if(d.depth === 0) return 0;
            return d.x < Math.PI ? 16 : -16;
        })
        .attr("text-anchor", function(d) {
            if(d.depth === 0) return "middle";
            return d.x < Math.PI ? "start" : "end";
        })
        .attr("transform", function(d) {
            if(d.depth === 0) return "rotate(0)";
            return "rotate(" + (d.x < Math.PI ? d.x - Math.PI / 2 : d.x + Math.PI / 2) * 180 / Math.PI + ")";
        })
        .text(function(d) {
            return d.data.name;
        })
        .style("fill-opacity", 1e-6);

    // Update
    var nodeUpdate = nodeEnter.merge(node).transition()
        .duration(600)
        .attr("transform", function(d) {
            return "translate(" + radialPoint(d.x, d.y) + ")";
        });

    nodeUpdate.select("circle")
        .attr("r", function(d) {
            if(d.depth === 0) {
                return 12;
            }else if(d.depth < 3) {
                return 10;
            }
            return 8;
        })
        .style("stroke-opacity", function(d) {
            if(d.children) {
                return 0.5;
            }else {
                return 0;
            }
        });

    nodeUpdate.select("path")
        .attr("d", function(d) {
            if(d.depth > 0 && d._children) {
                return "M-6 -1 H-1 V-6 H1 V-1 H6 V1 H1 V6 H-1 V1 H-6 Z";
            }else if(d.depth > 0 && d.children) {
                return "M-6 -1 H6 V1 H-6 Z";
            }
        })
        .style("fill-opacity", 1);

    nodeUpdate.select("text")
        .attr("x", function(d) {
            if(d.depth === 0) return 0;
            return d.x < Math.PI ? 16 : -16;
        })
        .attr("text-anchor", function(d) {
            if(d.depth === 0) return "middle";
            return d.x < Math.PI ? "start" : "end";
        })
        .attr("transform", function(d) {
            if(d.depth === 0) return "rotate(0)";
            return "rotate(" + (d.x < Math.PI ? d.x - Math.PI / 2 : d.x + Math.PI / 2) * 180 / Math.PI + ")";
        })
        .style("fill-opacity", 1);

    // Exit
    var nodeExit = node.exit().transition()
        .duration(600)
        .attr("transform", function(d) {
            return "translate(" + radialPoint(source.x, source.y) + ")";
        })
        .remove();

    nodeExit.select("circle")
        .attr("r", 1e-6);

    nodeExit.select("path")
        .style("fill-opacity", 0);

    nodeExit.select("text")
        .style("fill-opacity", 1e-6);

    var link = linkContainer.selectAll(".link")
        .data(links, function(d) { // node
            return d.id || (d.id = "link" + d.source.id + d.target.id);
        });

    var linkRadial = d3.linkRadial();
    var linkEnter = link.enter().append("path")
        .attr("class", "link")
        .attr("d", linkRadial
            .angle(function(d) {
                return source.x0;
            })
            .radius(function(d) {
                return source.y0;
            })
        );

    linkEnter.merge(link).transition()
        .duration(600)
        .attr("d", linkRadial
            .angle(function(d) {
                return d.x;
            })
            .radius(function(d) {
                return d.y;
            })
        );

    link.exit().transition()
        .duration(600)
        .attr("d", linkRadial
            .angle(function(d) {
                return source.x;
            })
            .radius(function(d) {
                return source.y;
            })
        )
        .remove();

    // Store the old positions for transition.
    nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
    });

}

function toggle(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else {
        d.children = d._children;
        d._children = null;
    }
    drawTree(d);
}


</script>
</div>

index.json

{
  "name": "Jerry & Mona Park",
  "parent": "null",
  "size": 10,
  "type": "steelblue",
  "level": "red",
  "children": [
    {
      "name": "James Morse Park",
      "parent": "Jerry & Mona Park",
      "size": 10,
      "type": "purple",
      "level": "purple",
      "children": [
        {
          "name": "Sarah Elise Park",
          "parent": "James Morse Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "Leah Corinne Park",
          "parent": "James Morse Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        }
      ]
    },
    {
      "name": "Cheryl Ann Park",
      "parent": "Jerry & Mona Park",
      "size": 10,
      "type": "steelblue",
      "level": "red",
      "children": [
        {
          "name": "Justin Allen Sheeley",
          "parent": "Cheryl Ann Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Zane William Allen Sheeley",
              "parent": "Justin Allen Sheeley",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        },
        {
          "name": "Cassie Marie Sheeley",
          "parent": "Cheryl Ann Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Carter Lee Ammon",
              "parent": "Cassie Marie Sheeley",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Remington Bennett",
              "parent": "Cassie Marie Sheeley",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        },
        {
          "name": "Trisha Lynn Sheeley",
          "parent": "Cheryl Ann Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Noah Simon",
              "parent": "Trisha Lynn Sheeley",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        }
      ]
    },
    {
      "name": "Robert Eugene Park",
      "parent": "Jerry & Mona Park",
      "size": 10,
      "type": "steelblue",
      "level": "red",
      "children": [
        {
          "name": "Brooke Whitnie Adams",
          "parent": "Robert Eugene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Riley Mckenna Bernuy",
              "parent": "Brooke Whitnie Adams",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Payson Gabriel Bernuy",
              "parent": "Brooke Whitnie Adams",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Sydnie Isabel Bernuy",
              "parent": "Brooke Whitnie Adams",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Kyson Mario Bernuy",
              "parent": "Brooke Whitnie Adams",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        },
        {
          "name": "Arial Jannelle Park",
          "parent": "Robert Eugene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "Colbren Robert Park",
          "parent": "Robert Eugene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "Alex Logan Park",
          "parent": "Robert Eugene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "Jordan Nathaniel Park",
          "parent": "Robert Eugene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Desmond Justin Park",
              "parent": "Jordan Nathaniel Park",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        },
        {
          "name": "Jacob Forrest Park",
          "parent": "Robert Eugene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        }
      ]
    },
    {
      "name": "Jolene Park",
      "parent": "Jerry & Mona Park",
      "size": 10,
      "type": "steelblue",
      "level": "red",
      "children": [
        {
          "name": "Jacqueline Rae Morgan",
          "parent": "Jolene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Mazer Ogden Palsson",
              "parent": "Jacqueline Rae Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Alana Rebekah Palsson",
              "parent": "Jacqueline Rae Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Manti Morgan Palsson",
              "parent": "Jacqueline Rae Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Lydia Faith Palsson",
              "parent": "Jacqueline Rae Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        },
        {
          "name": "Ryker Thomas Morgan",
          "parent": "Jolene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Oliver Thomas Morgan",
              "parent": "Ryker Thomas Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Indie Lynn Morgan",
              "parent": "Ryker Thomas Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Ari May Morgan",
              "parent": "Ryker Thomas Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        },
        {
          "name": "Katrina Morgan",
          "parent": "Jolene Park",
          "size": 10,
          "type": "steelblue",
          "level": "red",
          "children": [
            {
              "name": "Suri Lorna Willett",
              "parent": "Katrina Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            },
            {
              "name": "Jett Tucker Willett",
              "parent": "Katrina Morgan",
              "size": 10,
              "type": "steelblue",
              "level": "red"
            }
          ]
        }
      ]
    },
    {
      "name": "Samuel Frank Park",
      "parent": "Jerry & Mona Park",
      "size": 10,
      "type": "steelblue",
      "level": "red",
      "children": [
        {
          "name": "Drusilla Dawn Park",
          "parent": "Samuel Frank Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "Zachary Michael Park",
          "parent": "Samuel Frank Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "MacKenzie Marie Park",
          "parent": "Samuel Frank Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        },
        {
          "name": "Peyton Nicole Park",
          "parent": "Samuel Frank Park",
          "size": 10,
          "type": "steelblue",
          "level": "red"
        }
      ]
    }
  ]
}