반응형
Collapsible Tree라는 그래프 라이브러리 기능 조사.
최소 요구 조건
- 각 노드에서 부가 설명이 표시되어야 함.
- 현재 위치 표시
- 검색 기능 필요
그러다보니 조사를 했는데, php의 기능으로 특정라이브러리를 찾았다.
모든 기능이 충족되는 라이브러리가 FamilyTreeJS인데, 이건 유료 라이브러리...
이거 적용해보았는데, 트리가 넓어지는 게 100개 이상되면 맛이 가더라.
구현은 되는데, 확장 기능등이 작동되지 않음.
https://balkan.app/FamilyTreeJS
최초로 찾은 라이브러리
https://bl.ocks.org/mbostock/4339083
해당 라이브러리에 미니맵 기능이 추가된 것.
https://bl.ocks.org/bwswedberg/464a7dbc471ee2a94dd6278bc7d94710
동일한 라이브러리에 미니맵이 완성된 것.
https://bl.ocks.org/jjzieve/a743242f46321491a950
내가 만든 최종 기본+검색+위치 통합된 코드, 미니맵이 죽음...
그리고 무엇보다 4시간 넘게 고생했는데, 부가설명이 없음...
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.found {
fill: #ff4136;
stroke: #ff4136;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
/*Just to ensure the select2 box is "glued" to the top*/
.search {
width: 100%;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.1.js" integrity="sha256-3zlB5s2uwoUzrXK3BT7AX3FyvojsraNFxCc2vC/7pNI="
crossorigin="anonymous"></script>
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/select2/3.5.0/select2.min.css">
</link>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/select2/3.5.0/select2.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!-- This will be attached to select2, only static element on the page -->
<div id="search"></div>
<!-- Main -->
<script type="text/javascript">
//basically a way to get the path to an object
function searchTree(obj, search, path) {
if (obj.name.toString() === search) { //if search is found return, add the object to the path and return it
path.push(obj);
return path;
} else if (obj.children || obj
._children) { //if children are collapsed d3 object will have them instantiated as _children
var children = (obj.children) ? obj.children : obj._children;
for (var i = 0; i < children.length; i++) {
path.push(obj); // we assume this path is the right one
var found = searchTree(children[i], search, path);
if (found) { // we were right, this should return the bubbled-up path from the first if statement
return found;
} else { //we were wrong, remove this parent from the path and continue iterating
path.pop();
}
}
} else { //not the right object, return false so it will continue to iterate in the loop
return false;
}
}
function extract_select2_data(node, leaves, index) {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
index = extract_select2_data(node.children[i], leaves, index)[0];
}
} else {
leaves.push({
id: ++index,
text: node.name.toString()
});
}
return [index, leaves];
}
var div = d3.select("body")
.append("div") // declare the tooltip div
.attr("class", "tooltip")
.style("opacity", 0);
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = window.innerHeight - margin.top - margin.bottom;
var i = 0,
duration = 750,
root,
select2_data;
var diameter = 960;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//recursively collapse children
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function openPaths(paths) {
for (var i = 0; i < paths.length; i++) {
if (paths[i].id !== "1") { //i.e. not root
paths[i].class = 'found';
if (paths[i]._children) { //if children are hidden: open them, otherwise: don't do anything
paths[i].children = paths[i]._children;
paths[i]._children = null;
}
update(paths[i]);
}
}
}
d3.json("mockup/re.json", function(error, values) {
root = values;
select2_data = extract_select2_data(values, [], 0)[1]; //I know, not the prettiest...
root.x0 = height / 2;
root.y0 = 0;
root.children.forEach(collapse);
update(root);
//init search box
$("#search").select2({
data: select2_data,
containerCssClass: "search"
});
});
//attach search box listener
$("#search").on("select2-selecting", function(e) {
var paths = searchTree(root, e.object.text, []);
if (typeof(paths) !== "undefined") {
openPaths(paths);
} else {
alert(e.object.text + " not found!");
}
})
d3.select(self.frameElement).style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// 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.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("circle")
.attr("r", 1e-6)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? 10 : 10;
})
.attr("dy", ".35em")
.attr("style", "display:block; white-space:nowrap;")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name
})
.style("fill-opacity", 1e-6)
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) {
if (d.class === "found") {
return "#ff4136"; //red
} else if (d._children) {
return "lightsteelblue";
} else {
return "#fff";
}
})
.style("stroke", function(d) {
if (d.class === "found") {
return "#ff4136"; //red
}
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// 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("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(duration)
.attr("d", diagonal)
.style("stroke", function(d) {
if (d.target.class === "found") {
return "#ff4136";
}
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(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;
});
}
</script>
</html>
728x90
'취업 > Tree Graph' 카테고리의 다른 글
[Tree] react-d3-tree 미니맵 만들기. (0) | 2022.12.13 |
---|---|
[React.JS] react-d3-tree 그래프 기술 검토 (0) | 2022.12.11 |