2

I'm trying to update this Org Chart visualisation to use V4 of d3 but I'm hitting problems. This JSFiddle uses V3 while this JSFiddle uses V4.

I've run into a few things that have changed, for instance the CSV parsing (lines 53-54):

// var data = d3.csv.parse(csvData);
var data = d3.csvParse(csvData);

And the calculation of diagonal (discussed here, lines 79-97):

/*
var diagonal = d3.svg.diagonal.radial()
    .projection(function(d) {
        return [d.y, d.x / 180 * Math.PI];
    });
*/
var diagonal = function n(n, i) {
    var u = t.call(this, n, i),
        o = e.call(this, n, i),
        a = (u.y + o.y) / 2,
        l = [u, {
            x: u.x,
            y: a
        }, {
            x: o.x,
            y: a
        }, o];
    return l = l.map(r), "M" + l[0] + "C" + l[1] + " " + l[2] + " " + l[3]
}

And the geting of nodes has changed (lines 211-212):

//var nodes = cluster.nodes(root);
var nodes = d3.hierarchy(root);

Now I seem to be hitting an issue with those nodes with this error message for line 216:

Uncaught TypeError: cluster.links is not a function

Any help greatly appreciated, I'm no slouch when it comes to JS but this is my first foray into d3 and I'm getting really rather lost :-(.

Community
  • 1
  • 1
annoyingmouse
  • 5,524
  • 1
  • 27
  • 45

1 Answers1

2

Live demo:

var csvData = `Associate,Manager
Matt Herman,John Smith
Jane Doe,John Smith
Adam Brown,John Smith
Susan Harris,John Smith
Mike Jones,John Smith
John Smith,Colin Krauss
Colin Krauss,Ashley Carlin
Ashley Carlin,Lia McDermott
Evan Park,Lia McDermott
Lauren Werner,Evan Park
Shane Waterson,Evan Park
Emma Smith,Evan Park
Mike Gregory,Evan Park
Jose Biggleman,Evan Park
Michelle Spektor,Evan Park
Juan Branch,Evan Park
John Orbase,Evan Park
Matt McCloud,Evan Park
Kelsey Carsen,Evan Park
Kelli Krazwinski,Colin Krauss
Stephanie Goldstien,Colin Krauss
Ryan Woolwine,Colin Krauss
Kyle Bohm,Colin Krauss
Sydney Yellen,Colin Krauss
Shankar Murjhree,Colin Krauss
Wayne Ellington,Colin Krauss
Dwight Folds,Colin Krauss
Ellen McGlynn,Colin Krauss
Nicolas Smith,Colin Krauss
Molly Ercole,Colin Krauss
Scott Hane,Colin Krauss
Regina McMahon,Colin Krauss
Skip Holden,Colin Krauss
Kadeem McPherson,Colin Krauss
Ray Ortiz,Colin Krauss
Janet Barnes,Colin Krauss
Holly Gold,Colin Krauss
Lance Martinez,Ashley Carlin
Mike Lubow,Ashley Carlin
Jordan Belsin,Ashley Carlin
Tom Strithers,Ashley Carlin
Jamie Raleigh,Ellen McGlynn
Joseph Bowman,Ellen McGlynn
Kylie Branch,Ellen McGlynn
Lars Randall,Ellen McGlynn
Carlos Barndt,Lia McDermott
Leo Hastings,Lia McDermott
Jaime Kellemen,Lia McDermott
Harvey Klien,Lia McDermott
Lia McDermott,Lia McDermott`;


var data = d3.csvParse(csvData);

var height = document.getElementById("tree-container").offsetHeight;
var width = document.getElementById("tree-container").offsetWidth;
var avatarRadius = 20;
var translateOffset = 25;
var radius = d3.min([height, width]) / 2;
var cluster = d3.cluster()
    .size([360, radius / 1.33])
    // .separation(function(a,b){return (a.parent == b.parent ? 1:2)/a.depth;});

var svg = d3.select("#tree-container").append("svg")
    .attr("width", radius * 2)
    .attr("height", radius * 2)
    .attr("id", "tree-container-svg")
    .append("g")
    .attr("transform", "translate(" + radius + "," + height / 2 + ")");

//Clip path needed for cicrular SVG avatars
var defs = svg.append('defs');
var clipPath = defs.append('clipPath')
    .attr('id', 'clip-circle')
    .append('circle')
    .attr('r', avatarRadius - 2.5);

function project(x, y) {
        var angle = (x - 90) / 180 * Math.PI,
            radius = y;
        return [radius * Math.cos(angle), radius * Math.sin(angle)];
}
var diagonal = function (d) {
    return "M" + project(d.x, d.y) + "C" + project(d.x, (d.y + d.parent.y) / 2) +
        " " + project(d.parent.x, (d.y + d.parent.y) / 2) +
        " " + project(d.parent.x, d.parent.y);
}
d3.selection.prototype.moveToFront = function () {
    return this.each(function () {
        this.parentNode.appendChild(this);
    });
};

d3.selection.prototype.moveToBack = function () {
    return this.each(function () {
        var firstChild = this.parentNode.firstChild;
        if (firstChild) {
            this.parentNode.insertBefore(this, firstChild);
        }
    });
};

//http://www.d3noob.org/2014/01/tree-diagrams-in-d3js_11.html
function treeify(list, callback) {

    var dataMap = list.reduce(function (map, node) {
        map[node.Associate] = node;
        return map;
    }, {});

    var treeData = [];
    list.forEach(function (node) {
        //Assuming the highest node is the last in the csv file
        if (node.Manager === node.Associate) {
            node.Manager = "Board of Directors"
            callback(node);
        }
        // add to parent
        var parent = dataMap[node.Manager];

        if (parent) {
            // create child array if it doesn't exist
            (parent.children || (parent.children = []))
            // add node to child array
            .push(node);
        } else {
            // parent is null or missing
            treeData.push(node);
        }
    });
};

function findItem(root, name, callback) {
    var stack = [];
    stack.push(root);
    while (stack.length !== 0) {
        var element = stack.pop();
        if (element.Associate === name) {
            callback(element);
            return;
        }
        //The up, uncompressed case
        else if (element.children !== undefined && element.children.length > 0) {
            for (var i = 0; i < element.children.length; i++) {
                stack.push(element.children[i]);
            }
        }
        //The down (compressed) case
        else if (element._children !== undefined && element._children.length > 0) {
            for (var j = 0; j < element._children.length; j++) {
                stack.push(element._children[j]);
            }
        }
    }
}

function defaultPlot(root, elem) {
    findItem(root, elem, function (d) {
        //Showing 1 up and below
        findItem(root, d.Manager, function (x) {
            (x.children) ? x.children.forEach(collapse): x.children = x._children;
            drawIt(x, root);
        })
    })
}

function collapse(d) {
    if (d.children) {
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = undefined;
    }
}

//For the buggy transition interruption with many nodes
function showAllCurrentPathsAndNodes() {
    d3.selectAll(".link").style("opacity", 1);
    d3.selectAll(".node").style("opacity", 1);
}

// Toggle children on click.
function clickedNode(d, root) {
    //Accounting for the transition bug on the delay   
    showAllCurrentPathsAndNodes();

    if (d.children) {
        d._children = d.children;
        d.children = undefined;
        drawIt(root)
    } else {
        d.children = d._children;
        d._children = undefined;
        drawIt(root)
    }
}

//http://bl.ocks.org/syntagmatic/4092944
function drawIt(root) {    
    var nodes = d3.hierarchy(root);
    cluster(nodes);

    var links = nodes.descendants().slice(1);
  
    var link = svg.selectAll("path.link").data(links);

    var node = svg.selectAll("g.node").data(nodes.descendants(),function(d){
        return d.data.Associate;
    });


    link.transition().duration(1000).attr("d", diagonal);


    d3.selectAll(".node-cicle").classed("highlight", false);

    showAllCurrentPathsAndNodes();
    

    link.enter().append("path")
        .attr("class", "link")
        .attr("d", diagonal)
        .attr("", function (d) {
            d3.select(this).moveToBack();
        })
        .style("opacity", 0)
        .transition()
        .duration(300)
        .delay(function (d, i) {
            return 28 * i;
        }).style("opacity", 1);

    node.transition().duration(800).attr("transform", function (d) {
        return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
    });

    var g = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", function (d) {
            return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
        })
        .style("opacity", 0)
        .style("cursor", function (d) {
            d=d.data;
            return ((d._children || d.children) && d.Manager !== "Board of Directors") ? "pointer" : "not-allowed";
        })
        .on("mouseover", function () {
            d3.select(this).moveToFront();
        })

    //Cant trust the enter append here, reassign the event listener for all nodes each draw
    d3.selectAll(".node")
        .on("click", function (d) {
            d=d.data;
            return ((d._children || d.children) && d.Manager !== "Board of Directors") ? clickedNode(d, root) : "";
        });


    g.transition().duration(300)
        .delay(function (d, i) {
            return 28 * i;
        })
        .style("opacity", 1);

    g.append("circle")
        .attr("r", avatarRadius)
        .attr("class", "circle-marker")
        .style("stroke", function (d) {
            d = d.data;
            return ((d._children || d.children) && d.Manager !== "Board of Directors") ? "steelblue" : "gray";
        })
        .style("fill", function (d) {
            d = d.data;
            return ((d._children || d.children) && d.Manager !== "Board of Directors") ? "steelblue" : "#fff";
        });

    g.append("svg:image")
        .attr("class", "node-avatar")
        .attr("xlink:href", "http://safariuganda.com/wp-content/uploads/2014/12/480px-Facebook-default-no-profile-pic.jpg")
        .attr("height", avatarRadius * 2)
        .attr("width", avatarRadius * 2)
        .attr("x", "-" + avatarRadius)
        .attr("y", "-" + avatarRadius)
        .attr('clip-path', 'url(#clip-circle)');

    //Might want to tween this?
    d3.selectAll(".node-avatar")
        .attr("transform", function (d) {
            return "rotate(" + (-1 * (d.x - 90)) + ")";
        });

    g.append("text")
        .attr("dy", ".31em")
        .attr("class", "label-text")
        .text(function (d) {
            return d.data.Associate;
        })

    //search all labels to ensure they are right side up (cant rely on the enter append here)
    d3.selectAll(".label-text")
        .attr("text-anchor", function (d) {
            return d.x < 180 ? "start" : "end";
        })
        .attr("transform", function (d) {
            return d.x < 180 ? "translate(" + translateOffset + ")" : "rotate(180)translate(-" + translateOffset + ")";
        })

    link.exit().transition().duration(0).style("opacity", 0).remove();
    node.exit().transition().duration(0).style("opactiy", 0).remove();

}

treeify(data, function (treeReturn) {
    var root = treeReturn;
    defaultPlot(root, root.children[0].Associate)
});
html,
body {
    font-family: 'Open Sans', sans-serif;
    font-size: 12px;
    background-color: #fff;
    height: 100%;
    width: 100%;
    background-color: #f1f1f1;
    position: relative;
    display: block;
}

#tree-container {
    position: relative;
    display: block;
    margin-left: 100px;
    height: 100%;
    width: 100%;
}

.node circle {
    stroke-width: 1.5px;
}

.node {
    font: 10px sans-serif;
}

.link {
    fill: none;
    stroke: #ccc;
    stroke-width: 1.5px;
}

.label-text {
    -webkit-user-select: none;
    /* Chrome/Safari */
    -moz-user-select: none;
    /* Firefox */
    -ms-user-select: none;
    /* IE10+ */
    /* Rules below not implemented in browsers yet */
    -o-user-select: none;
    user-select: none;
}
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>test</title>

</head>

<body>
    <div id="tree-container"></div>
    <link rel="stylesheet" href="style.css">
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="index.js"></script>
</body>

</html>

How to migrate

var diagonal = d3.svg.diagonal.radial()
.projection(function(d) {
    return [d.y, d.x / 180 * Math.PI];
});

to

function project(x, y) {
    var angle = (x - 90) / 180 * Math.PI, radius = y;
    return [radius * Math.cos(angle), radius * Math.sin(angle)];
}

var diagonal = function (d) {
    return "M" + project(d.x, d.y) + "C" + project(d.x, (d.y + d.parent.y) / 2) +
    " " + project(d.parent.x, (d.y + d.parent.y) / 2) +
    " " + project(d.parent.x, d.parent.y);
}

And there're many changes in the drawIt method. Just refer to https://bl.ocks.org/mbostock/4739610f6d96aaad2fb1e78a72b385ab

blackmiaool
  • 5,234
  • 2
  • 22
  • 39