Javascript d3.js集群包含2600多个节点

Javascript d3.js集群包含2600多个节点,javascript,reactjs,typescript,d3.js,Javascript,Reactjs,Typescript,D3.js,我一直对节点的布局有问题。我复制了这个可观测的,它对较小的节点非常有效,但对于较大的节点,它似乎变得非常扭曲 我在一个带有typescript的react应用程序中使用这个。我没有可用的代码段,因为数据量很大 以下是一些问题和经验的屏幕广播: 这并不能迫使他们正确地进入他们的小组,而且动画效果也不太好 这是我的代码,如果有人能看到我是否缺少要更新/修复的内容: const groups = {}, childGroups: any = {}; child

我一直对节点的布局有问题。我复制了这个可观测的,它对较小的节点非常有效,但对于较大的节点,它似乎变得非常扭曲

我在一个带有typescript的react应用程序中使用这个。我没有可用的代码段,因为数据量很大

以下是一些问题和经验的屏幕广播:

这并不能迫使他们正确地进入他们的小组,而且动画效果也不太好

这是我的代码,如果有人能看到我是否缺少要更新/修复的内容:

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 };
const groups={},
子组:any={};
childGroups.children=[];
data.forEach(功能(项){
常量列表=组[项目组];
如果(列表){
列表。推送(项目);
}否则{
组[项目组]=[项目];
}
});
for(组中的常量属性){
if(组hasOwnProperty(属性)){
const children={
儿童:团体[财产]
};
childGroups.children.push(children);
}
}
常数包=()=>
d3.pack().size([width,height]).padding(1)(d3.hierarchy(childGroups).sum((d:any)=>d.size));
let节点:any=pack().leaves();
常数模拟=d3
.forceSimulation(节点)
.力('x',d3.力x(宽度/2).强度(0.1))
.力('y',d3.力(高度/2).强度(0.1))
.force('cluster',forceCluster())
.force('collide',forceCollide());
常量svg=d3
.选择(“#图表”)
.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)');
常量工具提示=d3
.选择(“#图表”)
.append('div'))
.style('opacity',0)
.attr('类','工具提示')
.style('background-color','#ececec')
.style('border-radius','5px')
.style('padding','8px');
const showTooltip=函数(此:any,d){
tooltip.transition().持续时间(200);
工具提示
.style(“不透明度”,0.8)
.html(d.data.tooltip)
.style('left',d3.鼠标(this)[0]+30+'px')
.style('top',d3.鼠标(this)[1]+30+'px')
.style('颜色',d.data.color);
};
const moveTooltip=函数(此:any,d){
tooltip.style('left',d3.mouse(this)[0]+30+'px')。style('top',d3.mouse(this)[1]+30+'px');
};
常量hideTooltip=函数(d){
tooltip.transition().duration(200).style('opacity',0);
};
const node=svg
.append('g')
.selectAll('圆圈')
.数据(节点)
.join('圆')
.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',hidetoltip);
node.transition()
.delay((d,i)=>Math.random()*500)
.持续时间(750)
.attrween('r',(d:any)=>{
常数i=d3.插值(0,d.r);
返回(t)=>(d.r=i(t));
});
simulation.on('tick',()=>{
node.attr('cx',(d:any)=>d.x.attr('cy',(d:any)=>d.y);
});
函数forceCluster(){
恒强度=0.2;
作用力(α){
常量质心:any=d3Array.rollup(节点,质心,(d:any)=>d.group);
常数l=α*强度;
for(节点的常数d){
常数{x:cx,y:cy}=centroids.get(d.group);
d、 vx-=(d.x-cx)*l;
d、 vy-=(d.y-cy)*l;
}
}
force.initialize=(\u)=>(节点=\ux);
返回力;
}
函数forceCollide(){
常数alpha=0.4;//固定为更大的刚性!
常量padding1=2;//相同颜色节点之间的分隔
常量padding2=6;//不同颜色节点之间的分隔
设最大半径;
函数力(){
常量四叉树=d3.quadTree(
节点,
(d:any)=>d.x,
(d) =>d.y
);
for(节点的常数d){
常数r=d.r+maxRadius;
常数nx1=d.x-r,
ny1=d.y-r;
常数nx2=d.x+r,
ny2=d.y+r;
四叉树访问((q:any,x1,y1,x2,y2)=>{
如果(!q.length)
做{
如果(q.data!==d){
常数r=
d、 r+q.data.r+(d.data.group==q.data.data.group?填充1:padding2);
设x=d.x-q.data.x,
y=d.y-q.data.y,
l=数学形下压(x,y);
if(lfunction 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;
}
node.transition()
    .delay((d, i) => 50)
    .duration(750);