javascript - 具有超过 2600 个节点的 d3.js 集群气泡
问题描述
我一直遇到节点布局问题。我复制了这个可观察的https://observablehq.com/@d3/clustered-bubbles,它适用于较小的节点,但对于较大的节点似乎真的失真了。
我在带有打字稿的反应应用程序中使用它。我没有可用的工作片段,因为数据很大。
这些是问题体验的一些截屏视频: https ://share.getcloudapp.com/lluJnbWl https://share.getcloudapp.com/jkuQYBpq
它不会强迫他们正确地进入他们的小组,并且动画效果不佳。
这是我的代码,如果有人可以看到我是否缺少要更新/修复的内容:
const groups = {},
childGroups: any = {};
childGroups.children = [];
data.forEach(function (item) {
const list = groups[item.group];
if (list) {
list.push(item);
} else {
groups[item.group] = [item];
}
});
for (const property in groups) {
if (groups.hasOwnProperty(property)) {
const children = {
children: groups[property]
};
childGroups.children.push(children);
}
}
const pack = () =>
d3.pack().size([width, height]).padding(1)(d3.hierarchy(childGroups).sum((d: any) => d.size));
let nodes: any = pack().leaves();
const simulation = d3
.forceSimulation(nodes)
.force('x', d3.forceX(width / 2).strength(0.1))
.force('y', d3.forceY(height / 2).strength(0.1))
.force('cluster', forceCluster())
.force('collide', forceCollide());
const svg = d3
.select('#chart')
.append('svg')
.attr('class', 'clusterGraph')
.attr('width', width)
.attr('height', height)
.attr('preserveAspectRatio', 'xMidYMid meet')
.attr('viewBox', `0 0 ${width} ${height}`)
.append('g')
.attr('transform', 'translate(0, 0)');
const tooltip = d3
.select('#chart')
.append('div')
.style('opacity', 0)
.attr('class', 'tooltip')
.style('background-color', '#ececec')
.style('border-radius', '5px')
.style('padding', '8px');
const showTooltip = function (this: any, d) {
tooltip.transition().duration(200);
tooltip
.style('opacity', 0.8)
.html(d.data.tooltip)
.style('left', d3.mouse(this)[0] + 30 + 'px')
.style('top', d3.mouse(this)[1] + 30 + 'px')
.style('color', d.data.color);
};
const moveTooltip = function (this: any, d) {
tooltip.style('left', d3.mouse(this)[0] + 30 + 'px').style('top', d3.mouse(this)[1] + 30 + 'px');
};
const hideTooltip = function (d) {
tooltip.transition().duration(200).style('opacity', 0);
};
const node = svg
.append('g')
.selectAll('circle')
.data(nodes)
.join('circle')
.attr('class', 'bubble')
.attr('cx', (d: any) => d.data.x)
.attr('cy', (d: any) => d.data.y)
.attr('fill', (d: any) => d.data.color)
.attr('r', (d: any) => d.r)
.on('mouseover', showTooltip)
.on('mousemove', moveTooltip)
.on('mouseleave', hideTooltip);
node.transition()
.delay((d, i) => Math.random() * 500)
.duration(750)
.attrTween('r', (d: any) => {
const i = d3.interpolate(0, d.r);
return (t) => (d.r = i(t));
});
simulation.on('tick', () => {
node.attr('cx', (d: any) => d.x).attr('cy', (d: any) => d.y);
});
function forceCluster() {
const strength = 0.2;
function force(alpha) {
const centroids: any = d3Array.rollup(nodes, centroid, (d: any) => d.group);
const l = alpha * strength;
for (const d of nodes) {
const { x: cx, y: cy } = centroids.get(d.group);
d.vx -= (d.x - cx) * l;
d.vy -= (d.y - cy) * l;
}
}
force.initialize = (_) => (nodes = _);
return force;
}
function forceCollide() {
const alpha = 0.4; // fixed for greater rigidity!
const padding1 = 2; // separation between same-color nodes
const padding2 = 6; // separation between different-color nodes
let maxRadius;
function force() {
const quadTree = d3.quadtree(
nodes,
(d: any) => d.x,
(d) => d.y
);
for (const d of nodes) {
const r = d.r + maxRadius;
const nx1 = d.x - r,
ny1 = d.y - r;
const nx2 = d.x + r,
ny2 = d.y + r;
quadTree.visit((q: any, x1, y1, x2, y2) => {
if (!q.length)
do {
if (q.data !== d) {
const r =
d.r + q.data.r + (d.data.group === q.data.data.group ? padding1 : padding2);
let x = d.x - q.data.x,
y = d.y - q.data.y,
l = Math.hypot(x, y);
if (l < r) {
l = ((l - r) / l) * alpha;
return (d.x -= x *= l), (d.y -= y *= l);
}
if (l < r) {
l = ((l - r) / l) * alpha;
return (q.data.x += x), (q.data.y += y);
}
}
} while ((q = q.next));
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
}
}
force.initialize = (_) => (maxRadius = Math.max(...nodes.map((o) => o.r)) + Math.max(padding1, padding2));
return force;
}
function centroid(nodes) {
let x = 0;
let y = 0;
let z = 0;
for (const d of nodes) {
const k = d.r ** 2;
x += d.x * k;
y += d.y * k;
z += k;
}
return { x: x / z, y: y / z };
任何帮助,将不胜感激。
解决方案
所以对于任何有同样问题的人,我们意识到我们在forceCluster
方法中使用了不正确的字段:
function forceCluster(nodes) {
const strength = 0.2;
function force(alpha) {
const centroids: any = d3Array.rollup(nodes, centroid, (d: any) => d.data.group);
const l = alpha * strength;
for (const d of nodes) {
const { x: cx, y: cy } = centroids.get(d.data.group);
d.vx -= (d.x - cx) * l;
d.vy -= (d.y - cy) * l;
}
}
force.initialize = (_) => (nodes = _);
return force;
}
就这样d.group
变成了d.data.group
。
我们还更改了节点动画:
node.transition()
.delay((d, i) => 50)
.duration(750);
推荐阅读
- java - Fire-base 卡住并且无法与 android studio 连接
- sql - Hana sql 如果左侧包含空且右侧包含值,则将单元格在 sql 中向左移动
- python - 如何为图形添加键?
- javascript - jQuery 调用 .GET() 两次并获得结果
- c - scanf 未正确读取 int
- python - 按值将多维数组拆分为另一个
- r - 在 ggplot2 中等效于地图的 eqscplot (geom_sf & coord_sf)
- javascript - 如何以正确的方式进行代码拆分?
- python - WeasyPrint HTML到图像转换:如何使图像大小适应内容?
- c - Scanf 更改 C 中的输入