Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Employees Hierarchy Chart using d3.js
license: mit
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" type="image/x-icon" href="https://production-assets.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" />
<link rel="mask-icon" type="" href="https://production-assets.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" />
<title>CodePen - Redesigned - Company Employees Hierarchy Chart </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel='stylesheet prefetch' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.6.3/css/font-awesome.css'>
<link rel='stylesheet prefetch' href='https://fonts.googleapis.com/css?family=Roboto'>
<style>
.full-container {
background-color: red;
width: 100%;
height: 100%;
font-family: 'Roboto', sans-serif;
}
/* ######################## DEPARTMENT INFO ############################*/
.department-information {
font-family: 'Roboto', sans-serif;
display:none;
box-shadow: 0 0 5px #999999;
position: absolute;
max-width: 200px;
top: 60px;
left: 20px;
padding: 10px;
background-color: white;
}
.department-information .dept-name {
color: #26a69a;
font-weight: bold;
}
.department-information .dept-description {
margin-top: 10px;
color: #959b9a;
font-size: 13px;
}
.department-information .dept-emp-count {
margin-top: 10px;
color: #959b9a;
font-size: 13px;
}
/* ############################## SEARCHBOX ######################################### */
.user-search-box {
overflow: hidden;
position: absolute;
right: 0;
height: 100%;
top: 0;
width: 0;
background-color: white;
border: 1px solid #c7dddb;
font-family: 'Roboto', sans-serif;
font-size: 14px;
line-height: 1.5;
}
::-webkit-input-placeholder {
/* WebKit, Blink, Edge */
color: #bcbcc4;
opacity: 0.5;
}
:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
color: #bcbcc4;
opacity: 0.5;
}
::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: #bcbcc4;
opacity: 0.5;
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: #bcbcc4;
opacity: 0.5;
}
.user-search-box .input-box {
width: 100%;
height: 200px;
top: 0;
background-color: #e8efee;
}
.user-search-box .close-button-wrapper i {
margin: 10px;
margin-left: 9%;
font-size: 60px;
font-weight: 400;
color: #aa1414;
}
.user-search-box input {
color: gray !important;
background-color: transparent;
border: none;
border-bottom: 1px solid #9e9e9e;
border-radius: 0;
outline: none;
height: 3rem;
width: 100%;
font-size: 1rem;
margin: 0 0 20px 0;
padding: 0;
box-shadow: none;
box-sizing: content-box;
transition: all 0.3s;
}
.user-search-box input:focus {
border-bottom: 1px solid #26a69a;
box-shadow: 0 1px 0 0 #26a69a;
}
.user-search-box .result-header {
background-color: white;
font-weight: 700;
padding: 12px;
color: gray;
border-top: 2px solid #d3e8e5;
border-bottom: 1px solid #d3e8e5;
}
.user-search-box .result-list {
position: absolute;
max-height: 100%;
min-width: 100%;
overflow: auto;
}
.user-search-box .buffer {
width: 100%;
height: 400px;
}
.user-search-box .list-item {
clear: both;
background-color: white;
position: relative;
background-color: white;
width: 100%;
height: 100px;
border-top: 1px solid #d3e8e5;
}
.user-search-box .list-item a {
display: inline;
margin: 0;
}
.user-search-box .list-item .image-wrapper {
float: left;
width: 100px;
height: 100px;
}
.user-search-box .list-item .image {
width: 70px;
height: 70px;
margin-left: 15px;
margin-top: 15px;
border-radius: 5px;
}
.user-search-box .list-item .description {
padding: 15px;
padding-left: 0px;
float: left;
width: 180px;
}
.user-search-box .list-item .buttons {
padding: 15px;
padding-left: 0px;
float: left;
width: auto;
}
.user-search-box .list-item .description .name {
font-size: 15px;
color: #aa1414;
font-weight: 900;
margin: 0;
padding: 0;
letter-spacing: 1px;
}
.user-search-box .list-item .description .position-name {
color: #59525b;
letter-spacing: 1px;
font-size: 12px;
font-weight: 900;
margin: 0;
margin-top: 3px;
padding: 0;
}
.user-search-box .list-item .description .area {
color: #91a4a5;
letter-spacing: 1px;
font-size: 12px;
font-weight: 400;
margin: 0;
margin-top: 3px;
padding: 0;
}
.user-search-box .list-item .btn-locate{
margin-top:30px;
}
.user-search-box .list-item .btn-search-box{
font-size:10px;
}
.user-search-box .close-button-wrapper i:hover {
color: black;
cursor: pointer;
}
.user-search-box .input-wrapper {
width: 80%;
margin: 0 auto;
}
.user-search-box .input-bottom-placeholder {
margin-top: -16px;
color: #bcbcc4;
letter-spacing: 1px;
}
/* ############################### Tooltip css ########################### */
.profile-image-wrapper {
background-size: 210px;
margin: 30px;
border-radius: 50%;
width: 210px;
height: 210px;
}
.customTooltip-wrapper {
font-family: 'Roboto', sans-serif;
opacity: 0;
/* NEW */
display: none;
position: absolute;
}
.customTooltip {
background: white;
box-shadow: 0 0 5px #999999;
color: #333;
position: absolute;
font-size: 12px;
left: 130px;
text-align: center;
top: 95px;
z-index: 10;
text-align: left;
}
.tooltip-hr {
width: 70px;
background-color: #91a4a5;
height: 1px;
margin-left: auto;
margin-right: auto;
margin-top: -17px;
margin-bottom: 25px;
}
.tooltip-desc {
padding-left: 10px;
margin-top: -20px;
margin-left: 20px;
overflow: auto;
}
.tooltip-desc .name {
color: #962828;
font-weight: 900;
letter-spacing: 1px;
font-size: 24px;
font-weight: bold;
margin-bottom: 2px;
text-decoration: none;
}
.tooltip-desc .name:hover {
text-decoration: underline;
}
.tooltip-desc .position {
color: #59525b;
letter-spacing: 1px;
font-size: 17px;
font-weight: 500;
margin-bottom: 2px;
margin-top: 0px;
}
.tooltip-desc .area {
color: #91a4a5;
letter-spacing: 1px;
font-size: 16px;
font-weight: 400;
margin-bottom: 2px;
margin-top: 7px;
}
.tooltip-desc .office {
color: #91a4a5;
line-height: 160%;
font-size: 14px;
font-weight: 400;
margin-bottom: -10px;
margin-top: -5px;
}
.tooltip-desc .tags-wrapper .title {
display: inline-block;
float: left;
}
.tooltip-desc .tags-wrapper .tags {
display: inline-block;
float: left;
}
.bottom-tooltip-hr {
width: 100%;
background-color: #58993e;
height: 3px;
margin-left: auto;
margin-right: auto;
margin-top: -17px;
}
.btn-tooltip-department {
margin-top: 20px;
}
.btn.disabled {
background-color: #DFDFDF !important;
box-shadow: none;
color: #9F9F9F !important;
cursor: default;
}
.btn {
border: none;
border-radius: 2px;
height: 36px;
line-height: 36px;
outline: 0;
text-transform: uppercase;
vertical-align: middle;
-webkit-tap-highlight-color: transparent;
text-decoration: none;
color: #fff;
background-color: #26a69a;
text-align: center;
letter-spacing: .5px;
transition: .2s ease-out;
cursor: pointer;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
}
.btn:hover {
box-shadow: 0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15);
}
.btn.disabled:hover {
box-shadow: none;
}
/* ####################################### TAGS ###################################### */
.tags {
list-style: none;
margin-top: -9px;
margin-left: 5px;
overflow: hidden;
padding: 0;
}
.tags-wrapper {
font-size: 2.28rem;
line-height: 110%;
margin: 1.14rem 0 0.912rem 0;
}
.tags-wrapper .title {
color: #91a4a5;
font-size: 24px;
}
.tags li {
float: left;
}
.tag {
font-size: 11px;
background: #E1ECF4;
border-radius: 2px;
color: ##39739d;
display: inline-block;
height: 20px;
line-height: 20px;
padding: 0 5px 0 5px;
position: relative;
margin: 0 5px 5px 0;
text-decoration: none;
-webkit-transition: color 0.2s;
}
/* ############################# Buttons ############################################*/
.btn-search {
top: 80px;
}
.btn-fullscreen {
top: 20px;
}
.btn-back {
top: 20px;
left: 20px;
display: none;
}
.btn-show-my-self {
top: 50px;
}
.btn-action {
position: absolute;
right: 25px;
height: 26px;
color: white;
background-color: #aa1414;
border: 1px solid black;
border-radius: 12px;
cursor: pointer;
font-size: 15px;
font-family: 'Roboto', sans-serif;
}
.btn-action:focus {
outline: 0;
background-color: #aa1414;
}
.btn-action:hover {
background-color: #490b0b;
}
.btn-action i {
font-size: 14px;
}
.btn-action .icon {
background-color: #c19e45;
padding: 5px 6px 5px 6px;
border-radius: 11px;
margin-right: -7px;
}
/* ############################################## SVG ################################# */
.nodeHasChildren {
fill: white;
}
.nodeDoesNotHaveChildren {
fill: white;
}
.nodeRepresentsCurrentUser {
stroke: Chartreuse;
stroke-width: 3;
}
text {
fill: dimgray;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
.node {
cursor: pointer;
}
.node-collapse {
stroke: grey;
}
.node-collapse-right-rect {
fill: #70c645;
stroke: #70c645;
}
.node text {
fill: white;
font-family: "Segoe UI", Arial, sans-serif;
font-size: 10px;
}
.node circle {
stroke-width: 1px;
stroke: #70c645;
fill: #70c645;
}
.node-group .emp-name {
fill: #962828;
font-size: 12px;
font-weight: 600
}
.node-group .emp-position-name {
fill: #59525b;
font-size: 11px;
}
.node-group .emp-area {
fill: #91a4a5;
font-size: 10px;
}
.node-group .emp-count,
.node-group .emp-count-icon {
fill: #91a4a5;
font-size: 12px;
}
</style>
</head>
<body translate="no" >
<div id="full-container">
<button class="btn-action btn-fullscreen" onclick="params.funcs.toggleFullScreen()">Fullscreen <span class='icon'/> <i class="fa fa-arrows-alt" aria-hidden="true"></i></span></button>
<button class="btn-action btn-show-my-self" onclick="params.funcs.showMySelf()"> Show myself <span class='icon'/> <i class="fa fa-user" aria-hidden="true"></i></span></button>
<button class=" btn-action btn-search" onclick="params.funcs.search()"> Search <span class='icon'/> <i class="fa fa-search" aria-hidden="true"></i></span></button>
<button class=" btn-action btn-back" onclick="params.funcs.back()"> Back <span class='icon'/> <i class="fa fa-arrow-left" aria-hidden="true"></i></span></button>
<div class="department-information">
<div class="dept-name">
dept name
</div>
<div class="dept-emp-count">
dept description test, this is department description
</div>
<div class="dept-description">
dept description test, this is department description
</div>
</div>
<div class="user-search-box">
<div class="input-box">
<div class="close-button-wrapper"><i onclick="params.funcs.closeSearchBox()" class="fa fa-times" aria-hidden="true"></i></div>
<div class="input-wrapper">
<input type="text" class="search-input" placeholder="Search" />
<div class="input-bottom-placeholder">By Firstname, Lastname, Tags</div>
</div>
<div>
</div>
</div>
<div class="result-box">
<div class="result-header"> RESULTS </div>
<div class="result-list">
<div class="buffer"></div>
</div>
</div>
</div>
<div id="svgChart"></div>
<!--
<button class="btn btn-expand" onclick="params.funcs.expandAll()">Expand All</button>
-->
</div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<script >
var params = {
selector: "#svgChart",
dataLoadUrl: "https://raw.githubusercontent.com/bumbeishvili/Assets/master/Projects/D3/Organization%20Chart/redesignedChartLongData.json",
chartWidth: window.innerWidth-40,
chartHeight: window.innerHeight - 40,
funcs: {
showMySelf: null,
search: null,
closeSearchBox: null,
clearResult: null,
findInTree: null,
reflectResults: null,
departmentClick: null,
back: null,
toggleFullScreen: null,
locate:null
},
data: null
}
d3.json(params.dataLoadUrl, function(data) {
params.data = data;
params.pristinaData = JSON.parse(JSON.stringify(data));
drawOrganizationChart(params);
})
function drawOrganizationChart(params) {
listen();
params.funcs.showMySelf = showMySelf;
params.funcs.expandAll = expandAll;
params.funcs.search = searchUsers;
params.funcs.closeSearchBox = closeSearchBox;
params.funcs.findInTree = findInTree;
params.funcs.clearResult = clearResult;
params.funcs.reflectResults = reflectResults;
params.funcs.departmentClick = departmentClick;
params.funcs.back = back;
params.funcs.toggleFullScreen = toggleFullScreen;
params.funcs.locate=locate;
var attrs = {
EXPAND_SYMBOL: '\uf067',
COLLAPSE_SYMBOL: '\uf068',
selector: params.selector,
root: params.data,
width: params.chartWidth,
height: params.chartHeight,
index: 0,
nodePadding: 9,
collapseCircleRadius: 7,
nodeHeight: 80,
nodeWidth: 210,
duration: 750,
rootNodeTopMargin: 20,
minMaxZoomProportions: [0.05, 3],
linkLineSize: 180,
collapsibleFontSize: '10px',
userIcon: '\uf007',
nodeStroke: "#ccc",
nodeStrokeWidth: '1px'
}
var dynamic = {}
dynamic.nodeImageWidth = attrs.nodeHeight * 100 / 140;
dynamic.nodeImageHeight = attrs.nodeHeight - 2 * attrs.nodePadding;
dynamic.nodeTextLeftMargin = attrs.nodePadding * 2 + dynamic.nodeImageWidth
dynamic.rootNodeLeftMargin = attrs.width / 2;
dynamic.nodePositionNameTopMargin = attrs.nodePadding + 8 + dynamic.nodeImageHeight / 4 * 1
dynamic.nodeChildCountTopMargin = attrs.nodePadding + 14 + dynamic.nodeImageHeight / 4 * 3
var tree = d3.layout.tree().nodeSize([attrs.nodeWidth + 40, attrs.nodeHeight]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
debugger;
return [d.x + attrs.nodeWidth / 2, d.y + attrs.nodeHeight / 2];
});
var zoomBehaviours = d3.behavior
.zoom()
.scaleExtent(attrs.minMaxZoomProportions)
.on("zoom", redraw);
var svg = d3.select(attrs.selector)
.append("svg")
.attr("width", attrs.width)
.attr("height", attrs.height)
.call(zoomBehaviours)
.append("g")
.attr("transform", "translate(" + attrs.width / 2 + "," + 20 + ")");
//necessary so that zoom knows where to zoom and unzoom from
zoomBehaviours.translate([dynamic.rootNodeLeftMargin, attrs.rootNodeTopMargin]);
attrs.root.x0 = 0;
attrs.root.y0 = dynamic.rootNodeLeftMargin;
if (params.mode != 'department') {
// adding unique values to each node recursively
var uniq = 1;
addPropertyRecursive('uniqueIdentifier', function(v) {
return uniq++;
}, attrs.root);
}
expand(attrs.root);
if (attrs.root.children) {
attrs.root.children.forEach(collapse);
}
update(attrs.root);
d3.select(attrs.selector).style("height", attrs.height);
var tooltip = d3.select('body')
.append('div')
.attr('class', 'customTooltip-wrapper');
function update(source, param) {
// Compute the new tree layout.
var nodes = tree.nodes(attrs.root)
.reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * attrs.linkLineSize;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++attrs.index);
});
// 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.x0 + "," + source.y0 + ")";
})
var nodeGroup = nodeEnter.append("g")
.attr("class", "node-group")
nodeGroup.append("rect")
.attr("width", attrs.nodeWidth)
.attr("height", attrs.nodeHeight)
.attr("data-node-group-id",function(d){
return d.uniqueIdentifier;
})
.attr("class", function(d) {
var res = "";
if (d.isLoggedUser) res += 'nodeRepresentsCurrentUser ';
res += d._children || d.children ? "nodeHasChildren" : "nodeDoesNotHaveChildren";
return res;
});
var collapsiblesWrapper =
nodeEnter.append('g')
.attr('data-id', function(v) {
return v.uniqueIdentifier;
});
var collapsibleRects = collapsiblesWrapper.append("rect")
.attr('class', 'node-collapse-right-rect')
.attr('height', attrs.collapseCircleRadius)
.attr('fill', 'black')
.attr('x', attrs.nodeWidth - attrs.collapseCircleRadius)
.attr('y', attrs.nodeHeight - 7)
.attr("width", function(d) {
if (d.children || d._children) return attrs.collapseCircleRadius;
return 0;
})
var collapsibles =
collapsiblesWrapper.append("circle")
.attr('class', 'node-collapse')
.attr('cx', attrs.nodeWidth - attrs.collapseCircleRadius)
.attr('cy', attrs.nodeHeight - 7)
.attr("", setCollapsibleSymbolProperty);
//hide collapse rect when node does not have children
collapsibles.attr("r", function(d) {
if (d.children || d._children) return attrs.collapseCircleRadius;
return 0;
})
.attr("height", attrs.collapseCircleRadius)
collapsiblesWrapper.append("text")
.attr('class', 'text-collapse')
.attr("x", attrs.nodeWidth - attrs.collapseCircleRadius)
.attr('y', attrs.nodeHeight - 3)
.attr('width', attrs.collapseCircleRadius)
.attr('height', attrs.collapseCircleRadius)
.style('font-size', attrs.collapsibleFontSize)
.attr("text-anchor", "middle")
.style('font-family', 'FontAwesome')
.text(function(d) {
return d.collapseText;
})
collapsiblesWrapper.on("click", click);
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", attrs.nodePadding + 10)
.attr('class', 'emp-name')
.attr("text-anchor", "left")
.text(function(d) {
return d.name.trim();
})
.call(wrap, attrs.nodeWidth);
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", dynamic.nodePositionNameTopMargin)
.attr('class', 'emp-position-name')
.attr("dy", ".35em")
.attr("text-anchor", "left")
.text(function(d) {
var position = d.positionName.substring(0,27);
if(position.length<d.positionName.length){
position = position.substring(0,24)+'...'
}
return position;
})
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", attrs.nodePadding + 10 + dynamic.nodeImageHeight / 4 * 2)
.attr('class', 'emp-area')
.attr("dy", ".35em")
.attr("text-anchor", "left")
.text(function(d) {
return d.area;
})
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", dynamic.nodeChildCountTopMargin)
.attr('class', 'emp-count-icon')
.attr("text-anchor", "left")
.style('font-family', 'FontAwesome')
.text(function(d) {
if (d.children || d._children) return attrs.userIcon;
});
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin + 13)
.attr("y", dynamic.nodeChildCountTopMargin)
.attr('class', 'emp-count')
.attr("text-anchor", "left")
.text(function(d) {
if (d.children) return d.children.length;
if (d._children) return d._children.length;
return;
})
nodeGroup.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("rx", 3)
.attr('x', attrs.nodePadding)
.attr('y', 2 + attrs.nodePadding)
.attr('width', dynamic.nodeImageWidth)
.attr('fill', 'none')
.attr('height', dynamic.nodeImageHeight - 4)
nodeGroup.append("svg:image")
.attr('y', 2 + attrs.nodePadding)
.attr('x', attrs.nodePadding)
.attr('preserveAspectRatio', 'none')
.attr('width', dynamic.nodeImageWidth)
.attr('height', dynamic.nodeImageHeight - 4)
.attr('clip-path', "url(#clip)")
.attr("xlink:href", function(v) {
return v.imageUrl;
})
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(attrs.duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
//todo replace with attrs object
nodeUpdate.select("rect")
.attr("width", attrs.nodeWidth)
.attr("height", attrs.nodeHeight)
.attr('rx', 3)
.attr("stroke", function(d){
if(param && d.uniqueIdentifier== param.locate){
return '#a1ceed'
}
return attrs.nodeStroke;
})
.attr('stroke-width', function(d){
if(param && d.uniqueIdentifier== param.locate){
return 6;
}
return attrs.nodeStrokeWidth})
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(attrs.duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", attrs.nodeWidth)
.attr("height", attrs.nodeHeight)
// 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("x", attrs.nodeWidth / 2)
.attr("y", attrs.nodeHeight / 2)
.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(attrs.duration)
.attr("d", diagonal)
;
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(attrs.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;
});
if(param && param.locate){
var x;
var y;
nodes.forEach(function(d) {
if (d.uniqueIdentifier == param.locate) {
x = d.x;
y = d.y;
}
});
// normalize for width/height
var new_x = (-x + (window.innerWidth / 2));
var new_y = (-y + (window.innerHeight / 2));
// move the main container g
svg.attr("transform", "translate(" + new_x + "," + new_y + ")")
zoomBehaviours.translate([new_x, new_y]);
zoomBehaviours.scale(1);
}
if (param && param.centerMySelf) {
var x;
var y;
nodes.forEach(function(d) {
if (d.isLoggedUser) {
x = d.x;
y = d.y;
}
});
// normalize for width/height
var new_x = (-x + (window.innerWidth / 2));
var new_y = (-y + (window.innerHeight / 2));
// move the main container g
svg.attr("transform", "translate(" + new_x + "," + new_y + ")")
zoomBehaviours.translate([new_x, new_y]);
zoomBehaviours.scale(1);
}
/*################ TOOLTIP #############################*/
function getTagsFromCommaSeparatedStrings(tags) {
return tags.split(',').map(function(v) {
return '<li><div class="tag">' + v + '</div></li> '
}).join('');
}
function tooltipContent(item) {
var strVar = "";
strVar += " <div class=\"customTooltip\">";
strVar += " <!--";
strVar += " <div class=\"tooltip-image-wrapper\"> <img width=\"300\" src=\"https:\/\/raw.githubusercontent.com\/bumbeishvili\/Assets\/master\/Projects\/D3\/Organization%20Chart\/cto.jpg\"> <\/div>";
strVar += "-->";
strVar += " <div class=\"profile-image-wrapper\" style='background-image: url(" + item.imageUrl + ")'>";
strVar += " <\/div>";
strVar += " <div class=\"tooltip-hr\"><\/div>";
strVar += " <div class=\"tooltip-desc\">";
strVar += " <a class=\"name\" href='" + item.profileUrl + "' target=\"_blank\"> " + item.name + "<\/a>";
strVar += " <p class=\"position\">" + item.positionName + " <\/p>";
strVar += " <p class=\"area\">" + item.area + " <\/p>";
strVar += "";
strVar += " <p class=\"office\">" + item.office + "<\/p>";
strVar += " <button class='" + (item.unit.type == 'business' ? " disabled " : "") + " btn btn-tooltip-department' onclick='params.funcs.departmentClick(" + JSON.stringify(item.unit) + ")'>" + item.unit.value + "</button>";
strVar += " <h4 class=\"tags-wrapper\"> <span class=\"title\"><i class=\"fa fa-tags\" aria-hidden=\"true\"><\/i>";
strVar += " ";
strVar += " <\/span> <ul class=\"tags\">" + getTagsFromCommaSeparatedStrings(item.tags) + "<\/ul> <\/h4> <\/div>";
strVar += " <div class=\"bottom-tooltip-hr\"><\/div>";
strVar += " <\/div>";
strVar += "";
return strVar;
}
function tooltipHoverHandler(d) {
var content = tooltipContent(d);
tooltip.html(content);
tooltip.transition()
.duration(200).style("opacity", "1").style('display', 'block');
d3.select(this).attr('cursor', 'pointer').attr("stroke-width", 50);
var y = d3.event.pageY;
var x = d3.event.pageX;
//restrict tooltip to fit in borders
if (y < 220) {
y += 220 - y;
x += 130;
}
if(y>attrs.height-300){
y-=300-(attrs.height-y);
}
tooltip.style('top', (y - 300) + 'px')
.style('left', (x - 470) + 'px');
}
function tooltipOutHandler() {
tooltip.transition()
.duration(200)
.style('opacity', '0').style('display', 'none');
d3.select(this).attr("stroke-width", 5);
}
nodeGroup.on('click', tooltipHoverHandler);
nodeGroup.on('dblclick', tooltipOutHandler);
function equalToEventTarget() {
return this == d3.event.target;
}
d3.select("body").on("click", function() {
var outside = tooltip.filter(equalToEventTarget).empty();
if (outside) {
tooltip.style('opacity', '0').style('display', 'none');
}
});
}
// Toggle children on click.
function click(d) {
d3.select(this).select("text").text(function(dv) {
if (dv.collapseText == attrs.EXPAND_SYMBOL) {
dv.collapseText = attrs.COLLAPSE_SYMBOL
} else {
if (dv.children) {
dv.collapseText = attrs.EXPAND_SYMBOL
}
}
return dv.collapseText;
})
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
//########################################################
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
// ############################# Function Area #######################
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function addPropertyRecursive(propertyName, propertyValueFunction, element) {
if (element[propertyName]) {
element[propertyName] = element[propertyName] + ' ' + propertyValueFunction(element);
} else {
element[propertyName] = propertyValueFunction(element);
}
if (element.children) {
element.children.forEach(function(v) {
addPropertyRecursive(propertyName, propertyValueFunction, v)
})
}
if (element._children) {
element._children.forEach(function(v) {
addPropertyRecursive(propertyName, propertyValueFunction, v)
})
}
}
function departmentClick(item) {
hide(['.customTooltip-wrapper']);
if (item.type == 'department' && params.mode != 'department') {
//find third level department head user
var found = false;
var secondLevelChildren = params.pristinaData.children;
parentLoop:
for (var i = 0; i < secondLevelChildren.length; i++) {
var secondLevelChild = secondLevelChildren[i];
var thirdLevelChildren = secondLevelChild.children ? secondLevelChild.children : secondLevelChild._children;
for (var j = 0; j < thirdLevelChildren.length; j++) {
var thirdLevelChild = thirdLevelChildren[j];
if (thirdLevelChild.unit.value.trim() == item.value.trim()) {
clear(params.selector);
hide(['.btn-action']);
show(['.btn-action.btn-back', '.btn-action.btn-fullscreen', '.department-information']);
set('.dept-name', item.value);
set('.dept-emp-count', "Employees Quantity - " + getEmployeesCount(thirdLevelChild));
set('.dept-description', thirdLevelChild.unit.desc);
params.oldData = params.data;
params.data = deepClone(thirdLevelChild);
found = true;
break parentLoop;
}
}
}
if (found) {
params.mode = "department";
params.funcs.closeSearchBox();
drawOrganizationChart(params);
}
}
}
function getEmployeesCount(node) {
var count = 1;
countChilds(node);
return count;
function countChilds(node) {
var childs = node.children ? node.children : node._children;
if (childs) {
childs.forEach(function(v) {
count++;
countChilds(v);
})
}
}
}
function reflectResults(results) {
var htmlStringArray = results.map(function(result) {
var strVar = "";
strVar += " <div class=\"list-item\">";
strVar += " <a >";
strVar += " <div class=\"image-wrapper\">";
strVar += " <img class=\"image\" src=\"" + result.imageUrl + "\"\/>";
strVar += " <\/div>";
strVar += " <div class=\"description\">";
strVar += " <p class=\"name\">" + result.name + "<\/p>";
strVar += " <p class=\"position-name\">" + result.positionName + "<\/p>";
strVar += " <p class=\"area\">" + result.area + "<\/p>";
strVar += " <\/div>";
strVar += " <div class=\"buttons\">";
strVar += " <a target='_blank' href='"+result.profileUrl+"'><button class='btn-search-box btn-action'>View Profile<\/button><\/a>";
strVar += " <button class='btn-search-box btn-action btn-locate' onclick='params.funcs.locate("+result.uniqueIdentifier+")'>Locate <\/button>";
strVar += " <\/div>";
strVar += " <\/a>";
strVar += " <\/div>";
return strVar;
})
var htmlString = htmlStringArray.join('');
params.funcs.clearResult();
var parentElement = get('.result-list');
var old = parentElement.innerHTML;
var newElement = htmlString + old;
parentElement.innerHTML = newElement;
set('.user-search-box .result-header', "RESULT - " + htmlStringArray.length);
}
function clearResult() {
set('.result-list', '<div class="buffer" ></div>');
set('.user-search-box .result-header', "RESULT");
}
function listen() {
var input = get('.user-search-box .search-input');
input.addEventListener('input', function() {
var value = input.value ? input.value.trim() : '';
if (value.length < 3) {
params.funcs.clearResult();
} else {
var searchResult = params.funcs.findInTree(params.data, value);
params.funcs.reflectResults(searchResult);
}
});
}
function searchUsers() {
d3.selectAll('.user-search-box')
.transition()
.duration(250)
.style('width', '350px')
}
function closeSearchBox() {
d3.selectAll('.user-search-box')
.transition()
.duration(250)
.style('width', '0px')
.each("end", function() {
params.funcs.clearResult();
clear('.search-input');
});
}
function findInTree(rootElement, searchText) {
var result = [];
// use regex to achieve case insensitive search and avoid string creation using toLowerCase method
var regexSearchWord = new RegExp(searchText, "i");
recursivelyFindIn(rootElement, searchText);
return result;
function recursivelyFindIn(user) {
if (user.name.match(regexSearchWord) ||
user.tags.match(regexSearchWord)) {
result.push(user)
}
var childUsers = user.children ? user.children : user._children;
if (childUsers) {
childUsers.forEach(function(childUser) {
recursivelyFindIn(childUser, searchText)
})
}
};
}
function back() {
show(['.btn-action']);
hide(['.customTooltip-wrapper', '.btn-action.btn-back', '.department-information'])
clear(params.selector);
params.mode = "full";
params.data = deepClone(params.pristinaData)
drawOrganizationChart(params);
}
function expandAll() {
expand(root);
update(root);
}
function expand(d) {
if (d.children) {
d.children.forEach(expand);
}
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}
if (d.children) {
// if node has children and it's expanded, then display -
setToggleSymbol(d, attrs.COLLAPSE_SYMBOL);
}
}
function collapse(d) {
if (d._children) {
d._children.forEach(collapse);
}
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
if (d._children) {
// if node has children and it's collapsed, then display +
setToggleSymbol(d, attrs.EXPAND_SYMBOL);
}
}
function setCollapsibleSymbolProperty(d) {
if (d._children) {
d.collapseText = attrs.EXPAND_SYMBOL;
} else if (d.children) {
d.collapseText = attrs.COLLAPSE_SYMBOL;
}
}
function setToggleSymbol(d, symbol) {
d.collapseText = symbol;
d3.select("*[data-id='" + d.uniqueIdentifier + "']").select('text').text(symbol);
}
/* recursively find logged user in subtree */
function findmySelf(d) {
if (d.isLoggedUser) {
expandParents(d);
} else if (d._children) {
d._children.forEach(function(ch) {
ch.parent = d;
findmySelf(ch);
})
} else if (d.children) {
d.children.forEach(function(ch) {
ch.parent = d;
findmySelf(ch);
});
};
}
function locateRecursive(d,id) {
if (d.uniqueIdentifier == id) {
expandParents(d);
} else if (d._children) {
d._children.forEach(function(ch) {
ch.parent = d;
locateRecursive(ch,id);
})
} else if (d.children) {
d.children.forEach(function(ch) {
ch.parent = d;
locateRecursive(ch,id);
});
};
}
/* expand current nodes collapsed parents */
function expandParents(d) {
while (d.parent) {
d = d.parent;
if (!d.children) {
d.children = d._children;
d._children = null;
setToggleSymbol(d, attrs.COLLAPSE_SYMBOL);
}
}
}
function toggleFullScreen() {
if ((document.fullScreenElement && document.fullScreenElement !== null) ||
(!document.mozFullScreen && !document.webkitIsFullScreen)) {
if (document.documentElement.requestFullScreen) {
document.documentElement.requestFullScreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullScreen) {
document.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
d3.select(params.selector + ' svg').attr('width', screen.width).attr('height', screen.height);
} else {
if (document.cancelFullScreen) {
document.cancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
d3.select(params.selector + ' svg').attr('width', params.chartWidth).attr('height', params.chartHeight);
}
}
function showMySelf() {
/* collapse all and expand logged user nodes */
if (!attrs.root.children) {
if (!attrs.root.isLoggedUser) {
attrs.root.children = attrs.root._children;
}
}
if (attrs.root.children) {
attrs.root.children.forEach(collapse);
attrs.root.children.forEach(findmySelf);
}
update(attrs.root, {centerMySelf:true});
}
//locateRecursive
function locate(id){
/* collapse all and expand logged user nodes */
if (!attrs.root.children) {
if (!attrs.root.uniqueIdentifier == id) {
attrs.root.children = attrs.root._children;
}
}
if (attrs.root.children) {
attrs.root.children.forEach(collapse);
attrs.root.children.forEach(function(ch){
locateRecursive(ch,id)
});
}
update(attrs.root, {locate:id});
}
function deepClone(item) {
return JSON.parse(JSON.stringify(item));
}
function show(selectors) {
display(selectors, 'initial')
}
function hide(selectors) {
display(selectors, 'none')
}
function display(selectors, displayProp) {
selectors.forEach(function(selector) {
var elements = getAll(selector);
elements.forEach(function(element) {
element.style.display = displayProp;
})
});
}
function set(selector, value) {
var elements = getAll(selector);
elements.forEach(function(element) {
element.innerHTML = value;
element.value = value;
})
}
function clear(selector) {
set(selector, '');
}
function get(selector) {
return document.querySelector(selector);
}
function getAll(selector) {
return document.querySelectorAll(selector);
}
}
</script>
</body>
</html>
@Jwu1993
Copy link

Jwu1993 commented Apr 6, 2022

Hi, the org chart looks awesome. This is exactly what we're looking for. But how do you build the nested json data file from a flat csv file? Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment