d3.js - 以树形布局绘制文件目录
d3.js - draw file directory with tree layout
文件目录结构看起来像一棵树,下面的代码尝试使用树形布局来绘制文件目录,就像linux平台中的树命令一样。
但看起来树布局在 BFS 模式而不是 DFS 模式下工作。
tree_as_directory()
function tree_as_directory() {
var data = `
* AA
** BB
*** EE
*** FF
** CC
*** GG
*** HH
*** II
** DD
*** JJ
**** LL
**** MM
*** KK
**** NN
**** O
`
var levels = []
var lines = data.split("\n")
lines = lines.filter(d => d.length > 0)
for (var i=0;i<lines.length;i++) {
var line = lines[i]
var regex = '^' + '([\*]+) ' + '([\w\s]+)'
var match = line.match(regex)
if (match != null) {
var num = match[1].length
var name = match[2]
levels.push({name:name,level:num})
}
}
var data = []
for (var i=0;i<levels.length;i++) {
var curr = data
for (var j=1;j<levels[i].level;j++) {
curr = curr[curr.length-1].children
}
var name = levels[i].name
curr.push({name:name,children:[]})
}
var root = data[0]
//console.log(data)
show_tree(root)
function show_tree(treeData) {
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var treemap = d3.tree()
.nodeSize([30,30])
//.size([height, width]);
var nodes = d3.hierarchy(treeData, function(d) {
return d.children;
});
nodes = treemap(nodes);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var curr = 0
var node = g.selectAll(".node")
.data(nodes)//nodes.descendants())
.enter().append("g")
.each(d => {
d.x = curr
curr += 15
})
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
node.append("text")
.attr("x", 0)
.style("text-anchor",'start')
.attr("dominant-baseline", "central")
.text(function(d) { return d.data.name; })
.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 0
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
d.bbox = bbox
})
function diagonal2(d) {
var ax = d.parent.x
var ay = d.parent.y
var bx = d.x
var by = d.y
var dir = 1
ax += d.parent.bbox.height/2
var path = ['M',ay,ax,'L',ay,bx,
,by,bx]
return path.join(' ')
}
var link = g.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr('stroke','#A80036')
.attr('stroke-width',1)
.attr('fill','none')
.attr("d", diagonal2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
您可以通过 node.eachBefore()
将函数传递给前序遍历中的层次结构节点。在这种情况下,我们可以很容易地使用层次结构编写我们自己的位置数据:
let y = 0;
nodes.eachBefore(function(d) {
d.x = d.depth * 20;
d.y = y++ * 20;
})
该片段使用您的约定(通常用于 D3 水平树):x 作为垂直值,y 作为水平值,对于上面的代码块,我没有把它发扬光大,因为它在孤立的情况下有点奇怪。
当然,我们现在真的不需要树布局,因为我们使用层次结构设置自己的定位,所以我将从下面的代码片段中删除它:
tree_as_directory()
function tree_as_directory() {
var data = `
* AA
** BB
*** EE
*** FF
** CC
*** GG
*** HH
*** II
** DD
*** JJ
**** LL
**** MM
*** KK
**** NN
**** O
`
var levels = []
var lines = data.split("\n")
lines = lines.filter(d => d.length > 0)
for (var i=0;i<lines.length;i++) {
var line = lines[i]
var regex = '^' + '([\*]+) ' + '([\w\s]+)'
var match = line.match(regex)
if (match != null) {
var num = match[1].length
var name = match[2]
levels.push({name:name,level:num})
}
}
var data = []
for (var i=0;i<levels.length;i++) {
var curr = data
for (var j=1;j<levels[i].level;j++) {
curr = curr[curr.length-1].children
}
var name = levels[i].name
curr.push({name:name,children:[]})
}
var root = data[0]
show_tree(root)
function show_tree(treeData) {
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var nodes = d3.hierarchy(treeData, function(d) {
return d.children;
});
// Start positioning:
let h = 0;
nodes.eachBefore(function(d) {
d.y = d.depth * 20;
d.x = h++ * 20;
})
// End positioning.
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var curr = 0
var node = g.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
node.append("text")
.attr("x", 0)
.style("text-anchor",'start')
.attr("dominant-baseline", "central")
.text(function(d) { return d.data.name; })
.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 0
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
d.bbox = bbox
})
function diagonal2(d) {
var ax = d.parent.x
var ay = d.parent.y
var bx = d.x
var by = d.y
var dir = 1
ax += d.parent.bbox.height/2
var path = ['M',ay,ax,'L',ay,bx,
,by,bx]
return path.join(' ')
}
var link = g.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr('stroke','#A80036')
.attr('stroke-width',1)
.attr('fill','none')
.attr("d", diagonal2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
文件目录结构看起来像一棵树,下面的代码尝试使用树形布局来绘制文件目录,就像linux平台中的树命令一样。 但看起来树布局在 BFS 模式而不是 DFS 模式下工作。
tree_as_directory()
function tree_as_directory() {
var data = `
* AA
** BB
*** EE
*** FF
** CC
*** GG
*** HH
*** II
** DD
*** JJ
**** LL
**** MM
*** KK
**** NN
**** O
`
var levels = []
var lines = data.split("\n")
lines = lines.filter(d => d.length > 0)
for (var i=0;i<lines.length;i++) {
var line = lines[i]
var regex = '^' + '([\*]+) ' + '([\w\s]+)'
var match = line.match(regex)
if (match != null) {
var num = match[1].length
var name = match[2]
levels.push({name:name,level:num})
}
}
var data = []
for (var i=0;i<levels.length;i++) {
var curr = data
for (var j=1;j<levels[i].level;j++) {
curr = curr[curr.length-1].children
}
var name = levels[i].name
curr.push({name:name,children:[]})
}
var root = data[0]
//console.log(data)
show_tree(root)
function show_tree(treeData) {
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var treemap = d3.tree()
.nodeSize([30,30])
//.size([height, width]);
var nodes = d3.hierarchy(treeData, function(d) {
return d.children;
});
nodes = treemap(nodes);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var curr = 0
var node = g.selectAll(".node")
.data(nodes)//nodes.descendants())
.enter().append("g")
.each(d => {
d.x = curr
curr += 15
})
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
node.append("text")
.attr("x", 0)
.style("text-anchor",'start')
.attr("dominant-baseline", "central")
.text(function(d) { return d.data.name; })
.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 0
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
d.bbox = bbox
})
function diagonal2(d) {
var ax = d.parent.x
var ay = d.parent.y
var bx = d.x
var by = d.y
var dir = 1
ax += d.parent.bbox.height/2
var path = ['M',ay,ax,'L',ay,bx,
,by,bx]
return path.join(' ')
}
var link = g.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr('stroke','#A80036')
.attr('stroke-width',1)
.attr('fill','none')
.attr("d", diagonal2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
您可以通过 node.eachBefore()
将函数传递给前序遍历中的层次结构节点。在这种情况下,我们可以很容易地使用层次结构编写我们自己的位置数据:
let y = 0;
nodes.eachBefore(function(d) {
d.x = d.depth * 20;
d.y = y++ * 20;
})
该片段使用您的约定(通常用于 D3 水平树):x 作为垂直值,y 作为水平值,对于上面的代码块,我没有把它发扬光大,因为它在孤立的情况下有点奇怪。
当然,我们现在真的不需要树布局,因为我们使用层次结构设置自己的定位,所以我将从下面的代码片段中删除它:
tree_as_directory()
function tree_as_directory() {
var data = `
* AA
** BB
*** EE
*** FF
** CC
*** GG
*** HH
*** II
** DD
*** JJ
**** LL
**** MM
*** KK
**** NN
**** O
`
var levels = []
var lines = data.split("\n")
lines = lines.filter(d => d.length > 0)
for (var i=0;i<lines.length;i++) {
var line = lines[i]
var regex = '^' + '([\*]+) ' + '([\w\s]+)'
var match = line.match(regex)
if (match != null) {
var num = match[1].length
var name = match[2]
levels.push({name:name,level:num})
}
}
var data = []
for (var i=0;i<levels.length;i++) {
var curr = data
for (var j=1;j<levels[i].level;j++) {
curr = curr[curr.length-1].children
}
var name = levels[i].name
curr.push({name:name,children:[]})
}
var root = data[0]
show_tree(root)
function show_tree(treeData) {
var margin = {top: 20, right: 90, bottom: 30, left: 90},
width = 660 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var nodes = d3.hierarchy(treeData, function(d) {
return d.children;
});
// Start positioning:
let h = 0;
nodes.eachBefore(function(d) {
d.y = d.depth * 20;
d.x = h++ * 20;
})
// End positioning.
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var curr = 0
var node = g.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", function(d) {
return "node" +
(d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")"; });
node.append("text")
.attr("x", 0)
.style("text-anchor",'start')
.attr("dominant-baseline", "central")
.text(function(d) { return d.data.name; })
.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 0
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
d.bbox = bbox
})
function diagonal2(d) {
var ax = d.parent.x
var ay = d.parent.y
var bx = d.x
var by = d.y
var dir = 1
ax += d.parent.bbox.height/2
var path = ['M',ay,ax,'L',ay,bx,
,by,bx]
return path.join(' ')
}
var link = g.selectAll(".link")
.data(nodes.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr('stroke','#A80036')
.attr('stroke-width',1)
.attr('fill','none')
.attr("d", diagonal2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>