d3. js Кластерные пузыри с более чем 2600 узлами - PullRequest
2 голосов
/ 20 июня 2020

У меня возникли проблемы с компоновкой узлов. Я скопировал этот наблюдаемый https://observablehq.com/@d3 / кластеризованные пузыри , и он отлично работает для меньших узлов, но, похоже, действительно искажается для больших узлов.

Я использую это в приложении для реагирования с машинописным текстом. У меня нет рабочего фрагмента, который можно было бы использовать, потому что данных очень много.

Вот некоторые скринкасты с описанием проблем: https://share.getcloudapp.com/lluJnbWl https://share.getcloudapp.com/jkuQYBpq

Это не заставляет их правильно объединяться в группы, и анимация работает не так хорошо.

Это мой код, если кто-то может увидеть, не хватает ли мне чего-то для обновления / fix:

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 };

Любая помощь приветствуется.

1 Ответ

0 голосов
/ 30 июня 2020

Итак, для всех, у кого возникла такая же проблема, мы поняли, что использовали неправильные поля для метода 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...