简介
Pack布局是一种用于表示包含与被包含关系的可视化布局方式,通常通过圆形套圆形来表示,其中外圆的大小表示被包含的对象,内圆的大小表示包含的对象。 Pack布局在D3.js中主要用于展示具有层级关系的数据,通过调整布局参数,如节点间距、节点半径等,可以控制布局的外观和视觉效果。
数据
// 数据地址:https://static.observableusercontent.com/files/85b8f86120ba5c8012f55b82fb5af4fcc9ff5e3cf250d110e111b3ab98c32a3fa8f5c19f956e096fbf550c47d6895783a4edf72a9c474bef5782f879573750ba
var flare = {
"name": "flare",
"children": []
}
主程序
function Pack(data, { // data可以是表格(对象数组)或层次结构(嵌套对象)
path, // 作为id和parentId的替代,返回一个数组标识符,输入内部节点
id = Array.isArray(data) ? d => d.id : null, // 如果数据中给定d的表格数据返回唯一标识符(字符串)
parentId = Array.isArray(data) ? d => d.parentId : null, // 如果给定节点d的表格数据返回其父节点的标识符
children, // 如果数据中给定d的分层数据返回其子数据
value, // 给定节点d,返回一个定量值(用于区域编码;null表示计数)
sort = (a, b) => d3.descending(a.value, b.value), // 如何在布局前对节点进行排序
label, // 给定一个叶子节点d,返回显示名称
title, // 给定节点d,返回其悬停文本
link, // 给定一个节点d,它的链接(如果有的话)
linkTarget = "_blank", // 链接的目标属性(如果有的话)
width = 640, // 外部宽、高、边距,像素
height = 400,
margin = 1,
marginTop = margin,
marginRight = margin,
marginBottom = margin,
marginLeft = margin,
padding = 3, // 圆之间的间距
fill = "#ddd", // 叶子圆的填充色、透明度、边色、宽度、透明度
fillOpacity,
stroke = "#bbb",
strokeWidth,
strokeOpacity,
} = {}) {
// 如果指定了id和parentId选项,或路径选项,请使用d3.stratify将表格数据转换为层次结构;
// 否则,我们假设数据指定为具有嵌套对象的对象{children}(即“flare.json”)格式),并使用d3.hierarchy。
const root = path != null ? d3.stratify().path(path)(data)
: id != null || parentId != null ? d3.stratify().id(id).parentId(parentId)(data)
: d3.hierarchy(data, children);
// 通过从叶子聚合来计算内部节点的值。
value == null ? root.count() : root.sum(d => Math.max(0, value(d)));
// 计算标签和标题。
const descendants = root.descendants();
const leaves = descendants.filter(d => !d.children);
leaves.forEach((d, i) => d.index = i);
const L = label == null ? null : leaves.map(d => label(d.data, d));
const T = title == null ? null : descendants.map(d => title(d.data, d));
// 对叶子进行排序(通常按降序排列,以获得令人愉悦的布局)。
if (sort != null) root.sort(sort);
// 计算布局。
d3.pack()
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
.padding(padding)
(root);
const svg = d3.create("svg")
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle");
const node = svg.selectAll("a")
.data(descendants)
.join("a")
.attr("xlink:href", link == null ? null : (d, i) => link(d.data, d))
.attr("target", link == null ? null : linkTarget)
.attr("transform", d => `translate(${d.x},${d.y})`);
node.append("circle")
.attr("fill", d => d.children ? "#fff" : fill)
.attr("fill-opacity", d => d.children ? null : fillOpacity)
.attr("stroke", d => d.children ? stroke : null)
.attr("stroke-width", d => d.children ? strokeWidth : null)
.attr("stroke-opacity", d => d.children ? strokeOpacity : null)
.attr("r", d => d.r);
if (T) node.append("title").text((d, i) => T[i]);
if (L) {
// 剪辑路径的唯一标识符(以避免冲突)。
const uid = `O-${Math.random().toString(16).slice(2)}`;
const leaf = node
.filter(d => !d.children && d.r > 10 && L[d.index] != null);
leaf.append("clipPath")
.attr("id", d => `${uid}-clip-${d.index}`)
.append("circle")
.attr("r", d => d.r);
leaf.append("text")
.attr("clip-path", d => `url(${new URL(`#${uid}-clip-${d.index}`, location)})`)
.selectAll("tspan")
.data(d => `${L[d.index]}`.split(/\n/g))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i, D) => `${(i - D.length / 2) + 0.85}em`)
.attr("fill-opacity", (d, i, D) => i === D.length - 1 ? 0.7 : null)
.text(d => d);
}
return svg.node();
}
var chart = Pack(flare, {
value: d => d.size, // 每个节点(文件)的大小;内部节点(文件夹)为null
label: (d, n) => [...d.name.split(/(?=[A-Z][a-z])/g), n.value.toLocaleString("en")].join("\n"),
title: (d, n) => `${n.ancestors().reverse().map(({data: d}) => d.name).join(".")}\n${n.value.toLocaleString("en")}`,
link: (d, n) => n.children
? `https://github.com/prefuse/Flare/tree/master/flare/src/${n.ancestors().reverse().map(d => d.data.name).join("/")}`
: `https://github.com/prefuse/Flare/blob/master/flare/src/${n.ancestors().reverse().map(d => d.data.name).join("/")}.as`,
width: 1152,
height: 1152
});
var myBody = document.getElementById('myBody');
myBody.appendChild(chart)