Built with blockbuilder.org
forked from willzjc's block: Employees Hierarchy Chart using d3.js
| license: mit |
Built with blockbuilder.org
forked from willzjc's block: Employees Hierarchy Chart using d3.js
| <!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> |