d3-org-chart
Version:
Highly customizable org chart, created with d3
381 lines (358 loc) • 14.3 kB
HTML
<html>
<head>
<meta charset="UTF-8" />
<title>OrgChart</title>
<link rel="shortcut icon" type="image/x-icon" href="misc/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<!---------------- ORG CHART NON ESSENTIAL CONTENT ------------>
<script src="https://unpkg.com/html2canvas@1.1.4/dist/html2canvas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.3.1/jspdf.umd.min.js"></script>
<link
href="https://cdn.jsdelivr.net/npm/simptip@1.0.4/simptip.min.css"
rel="stylesheet"
/>
<style>
[data-tooltip]:focus:after {
display: none;
border-bottom-color: none ;
}
[data-tooltip]:focus:before {
display: none;
border-bottom-color: none ;
}
[data-tooltip]:after {
height: auto ;
padding: 11px 11px 11px 11px ;
content: attr(data-tooltip);
white-space: pre;
line-height: 16px ;
text-align: left ;
}
</style>
<div class="action-buttons" style="margin: 0 100px">
<button
onclick='chart.setExpanded("103").render()'
class="simptip-position-bottom"
data-tooltip='chart.setExpanded("103") 
 .render()'
>
Expand #103
</button>
<button
onclick='chart.setExpanded("103",false).render()'
class="simptip-position-bottom"
data-tooltip='chart.setExpanded("103",false) 
 .render()'
>
Collapse #103
</button>
<button
onclick='chart.addNode({id:"root child",parentId:"100",name:"test-name",position:"test-position",_centered:true})'
class="simptip-position-bottom"
data-tooltip='chart.addNode({id:"root child",parentId:"100",name:"test name",position:"test position"})'
>
Add node as roots' child
</button>
<!-- <button onclick='chart.addNode({id:"5-th level node",parentId:"O-6164",_centered:true})'
class="simptip-position-bottom"
data-tooltip='chart.addNode({id:"5-th level node",parentId:"O-6164",_centered:true})'>Insert node at 5-th
level</button> -->
<button
onclick='chart.removeNode("120")'
class="simptip-position-bottom"
data-tooltip='chart.removeNode("120")'
>
Remove #120
</button>
<button
onclick="chart.fit()"
class="simptip-position-bottom"
data-tooltip="chart.fit()"
>
Fit
</button>
<button
onclick='chart.layout(["right","bottom","left","top"][index++%4]).render().fit()'
class="simptip-position-bottom"
data-tooltip='chart.layout(["right","bottom","left","top"][index++%4]) 
 .render() 
 .fit()'
>
Swap Layouts
</button>
<button
onclick='chart.setCentered("134").render()'
class="simptip-position-bottom"
data-tooltip='chart.setCentered("134") 
 .render()'
>
Center #134
</button>
<button
onclick='chart.setHighlighted("134").render()'
class="simptip-position-bottom"
data-tooltip='chart.setHighlighted("134") 
 .render()'
>
Highlight #134
</button>
<button
onclick='chart.setUpToTheRootHighlighted("134").render()'
class="simptip-position-bottom"
data-tooltip='chart.setUpToTheRootHighlighted("134") 
 .render()'
>
Highlight #134 to root
</button>
<button
onclick="chart.clearHighlighting()"
class="simptip-position-bottom"
data-tooltip="chart.clearHighlighting() 
 .render()"
>
Clear highlighting
</button>
<button
onclick="chart.fullscreen()"
class="simptip-position-bottom"
data-tooltip="chart.fullscreen()"
>
Full Screen
</button>
<button
onclick="chart.zoomIn()"
class="simptip-position-bottom"
data-tooltip="chart.zoomIn()"
>
Zoom In
</button>
<button
onclick="chart.zoomOut()"
class="simptip-position-bottom"
data-tooltip="chart.zoomOut()"
>
Zoom Out
</button>
<button
onclick="chart.exportImg()"
class="simptip-position-bottom"
data-tooltip="chart.exportImg()"
>
Export Current Image
</button>
<button
onclick="chart.exportImg({full:true})"
class="simptip-position-bottom"
data-tooltip="chart.exportImg({full:true})"
>
Export Export Full Image
</button>
<button
onclick="chart.exportSvg()"
class="simptip-position-bottom"
data-tooltip="chart.exportSvg()"
>
Export SVG
</button>
<button
onclick="chart.expandAll()"
class="simptip-position-bottom"
data-tooltip="chart.expandAll()"
>
Expand All
</button>
<button
onclick="chart.collapseAll()"
class="simptip-position-bottom"
data-tooltip="chart.collapseAll()"
>
Collapse All
</button>
<button
onclick='chart.connections([{from:"145",to:"201",label:"Conflicts of interest"}]).render()'
class="simptip-position-bottom"
data-tooltip='chart.connections({from:"",to:""}) 
 .render()'
>
Multi Node Connections
</button>
<button
onclick="downloadPdf()"
class="simptip-position-bottom"
data-tooltip="downloadPdf()"
>
Download PDF
</button>
<script>
function downloadPdf() {
chart.exportImg({
save: false,
onLoad: (base64) => {
var pdf = new jspdf.jsPDF();
var img = new Image();
img.src = base64;
img.onload = function () {
pdf.addImage(
img,
"JPEG",
5,
5,
595 / 3,
((img.height / img.width) * 595) / 3
);
pdf.save("chart.pdf");
};
},
});
}
</script>
</div>
<a href="https://github.com/bumbeishvili/d3-organization-chart">
<img
style="position: fixed; top: 0; right: 0; border: 0; z-index: 2"
width="149"
height="149"
src="https://bumbeishvili.github.io/d3-tooltip/forkme.png"
alt="Fork me on GitHub"
/>
</a>
<!---------------- ORG CHART ESSENTIAL CONTENT ---------------->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="./build/d3-org-chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-flextree@2.0.0/build/d3-flextree.js"></script>
<div
class="chart-container"
style="padding-top: 10px; height: 1200px; background-color: white"
></div>
<script>
var index = 0;
var chart;
d3.csv("misc/data.csv").then((dataFlattened) => {
chart = new d3.OrgChart()
.container(".chart-container")
.pagingStep((d) => 5)
.minPagingVisibleNodes((d) => 3)
.data(dataFlattened)
// .scaleExtent([1,1])
// .svgHeight(window.innerHeight)
// .nodeWidth((d3Node) => {
// return 200;
// let i = 0;
// if (d3Node.parent) { i = d3Node.parent.children.indexOf(d3Node); }
// if (i && i == d3Node.parent.children.length - 1) { return 600; }
// return (!i || i == d3Node.parent.children.length - 1) ? 300 : 100
// })
// .nodeHeight((d3Node) => {
// return 100;
// let i = 0;
// if (d3Node.parent) { i = d3Node.parent.children.indexOf(d3Node); }
// if (i && i == d3Node.parent.children.length - 1) { return 300; }
// return (!i || i == d3Node.parent.children.length - 1) ? 200 : 100
// })
// .siblingsMargin(d3Node => 0)
// .childrenMargin(d3Node => 50)
// .neighbourMargin((n1, n2) => 50)
// .compactMarginPair(d3Node => 70)
// .compactMarginBetween(d3Node => 30)
// .setActiveNodeCentered(true)
// .layout(new URLSearchParams(new URL(document.location.href).search).get('layout') || "top")
// .onNodeClick(d => console.log(d + ' node clicked'))
// .linkUpdate(function (d3Node, i, arr) {
// const link = this;
// d3.select(link)
// .attr('stroke-dasharray', !i ? '2 2' : 'none')
// .attr('stroke-width', 3)
// })
// .nodeUpdate(function (data, i, arr) {
// d3.select(this).on('click.node', (event, d, i) => {
// console.log(d.nodeId + ' node clicked')
// })
// })
// .connections(
// [
// { id: 1, from: "O-6067", to: "O-6068", label: "Directly Reports To" },
// { id: 2, from: "O-6070", to: "O-6066", label: "Reports To" },
// { id: 3, from: "O-6088", to: "O-6069", label: "They were coworkers once" },
// { id: 3, from: "O-6164", to: "O-6070", label: "Possible conflicts of interest" }
// ],
// )
// .nodeContent(function (d, i, arr, state) {
// return `
// <div style="padding:0px">
// <!--
// ${state.layout == 'top' && state.compact && d.flexCompactDim && (d.flexCompactDim[0] || d.flexCompactDim[0] == 1) ? ` <div style="border:1px solid black;opacity:0.5;margin-left:${-(d.flexCompactDim[0] / 2 - d.width) / 2 + state.compactMarginPair(d) / 4}px;width:${d.flexCompactDim[0]}px;height:${d.flexCompactDim[1]}px;z-index:-1;position:absolute;background-color:red"></div>` : ''}
// ${state.layout == 'bottom' && state.compact && d.flexCompactDim && (d.flexCompactDim[0] || d.flexCompactDim[0] == 1) ? ` <div style="border:1px solid black;opacity:0.5;margin-top:${-d.flexCompactDim[1] + d.height}px;margin-left:${-(d.flexCompactDim[0] / 2 - d.width) / 2 + state.compactMarginPair(d) / 4}px;width:${d.flexCompactDim[0]}px;height:${d.flexCompactDim[1]}px;z-index:-1;position:absolute;background-color:red"></div>` : ''}
// ${state.layout == 'left' && state.compact && d.flexCompactDim && (d.flexCompactDim[0] || d.flexCompactDim[0] == 1) ? ` <div style="border:1px solid black;opacity:0.5;margin-top:${-(d.flexCompactDim[0]/2-d.height)/2+ state.compactMarginPair(d) / 4}px;margin-left:${0}px;width:${d.flexCompactDim[1]}px;height:${d.flexCompactDim[0]}px;z-index:-1;position:absolute;background-color:red"></div>` : ''}
// ${state.layout == 'right' && state.compact && d.flexCompactDim && (d.flexCompactDim[0] || d.flexCompactDim[0] == 1) ? ` <div style="border:1px solid black;opacity:0.5; margin-top:${-(d.flexCompactDim[0]/2-d.height)/2+ state.compactMarginPair(d) / 4}px;margin-left:${d.width-d.flexCompactDim[1]}px;width:${d.flexCompactDim[1]}px;height:${d.flexCompactDim[0]}px;z-index:-1;position:absolute;background-color:red"></div>` : ''}
// -->
// <img src="${d.data.imageUrl}" style="border-radius:100px;width:60px;height:60px;" />
// ID: ${d.data.id} <br>
// Children Direct:${d.data._directSubordinates}<br>
// Children Total:${d.data._totalSubordinates}
// </div>
// `;
// })
.nodeHeight((d) => 85 + 25)
.nodeWidth((d) => {
return 220 + 2;
})
.childrenMargin((d) => 50)
.onNodeClick((d) => console.log("ok"))
// .layout('right')
.compactMarginBetween((d) => 35)
.compactMarginPair((d) => 30)
.neighbourMargin((a, b) => 20);
// .buttonContent(({ node, state }) => {
// return `<div style="color:#716E7B;border-radius:5px;padding:3px;font-size:10px;margin:auto auto;background-color:white;border: 1px solid #E4E2E9"> <span style="font-size:9px">${node.children ? `<i class="fas fa-angle-up"></i>` : `<i class="fas fa-angle-down"></i>`}</span> ${node.data._directSubordinates} </div>`
// })
// .linkUpdate(function (d, i, arr) {
// d3.select(this)
// .attr("stroke", d => d.data._upToTheRootHighlighted ? '#152785' : '#E4E2E9')
// .attr("stroke-width", d => d.data._upToTheRootHighlighted ? 5 : 1)
// if (d.data._upToTheRootHighlighted) {
// d3.select(this).raise()
// }
// })
// .nodeContent(function (d, i, arr, state) {
// const color = "#FFFFFF";
// const imageDiffVert = 25 + 2;
// return `
// <div style='width:${
// d.width
// }px;height:${d.height}px;padding-top:${imageDiffVert - 2}px;padding-left:1px;padding-right:1px'>
// <div style="font-family: 'Inter', sans-serif;background-color:${color}; margin-left:-1px;width:${d.width - 2}px;height:${d.height - imageDiffVert}px;border-radius:10px;border: 1px solid #E4E2E9">
// <div style="display:flex;justify-content:flex-end;margin-top:5px;margin-right:5px">#${
// d.data.id
// }</div>
// <div style="background-color:${color};margin-top:${-imageDiffVert - 20}px;margin-left:${15}px;border-radius:100px;width:50px;height:50px;" ></div>
// <div style="margin-top:${
// -imageDiffVert - 20
// }px;"> <img src=" ${d.data.image}" style="margin-left:${20}px;border-radius:100px;width:40px;height:40px;" /></div>
// <div style="font-size:15px;color:#08011E;margin-left:20px;margin-top:10px"> ${
// d.data.name
// } </div>
// <div style="color:#716E7B;margin-left:20px;margin-top:3px;font-size:10px;"> ${
// d.data.position
// } </div>
// </div>
// </div>
// `;
// });
chart.layoutBindings(
(() => {
const layouts = chart.layoutBindings();
layouts.top.linkY = (node) => node.y + 0;
layouts.top.linkCompactYStart = (node) => node.y + node.height / 2 + 15;
return layouts;
})()
);
chart.render();
});
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
rel="stylesheet"
/>
<style></style>
</body>
</html>