此文章仅用来记录3d-force-graph示例学习过程中的问题,以及相关笔记。

1.basic (基本)

const Graph = ForceGraph3D()(document.getElementById('3d-graph')).graphData(gData);

2.async-load (异步加载)

// URL scheme "file" is not supported. 未解决
const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .jsonUrl('../datasets/miserables.json')
            .nodeLabel('id')//鼠标悬浮至mesh上显示标签,标签内容未id
            .nodeAutoColorBy('group');//按照group进行分类

报错:URL scheme "file" is not supported. 未解决,改为:

const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData) //直接把json内容粘贴到gData
            .nodeLabel('id')
            .nodeAutoColorBy('group');

效果:

 3.larger-graph

新知识点:

onNodeClick:监听事件单击,在新页面打开      

const Graph = ForceGraph3D()(elem)
            .jsonUrl('../datasets/blocks.json')
            .nodeAutoColorBy('user')
            .nodeLabel(node => `${node.user}: ${node.description}`)
            .onNodeClick(
                node => window.open(`https://bl.ocks.org/${node.user}/${node.id}`, '_blank') //单击在新页面打开
            );

4.Directional arrows(带方向的箭头)

const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeLabel('id')
            .linkDirectionalArrowLength(3.5) //箭头长度
            .linkDirectionalArrowRelPos(1) //箭头位置偏移 source指向target
            .linkCurvature(0.25); //曲度

效果:

 5.Directional moving particles Directional moving particles(定向移动例粒子)

const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeLabel('id')
            .nodeAutoColorBy('group')
            .linkDirectionalParticles('value') //粒子个数
            .linkDirectionalParticleSpeed(d => d.value * 0.001); //粒子运动速度

效果(部分截图):

6.Curved lines and self links(曲线和自链接)

const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .nodeLabel('id')
            .linkCurvature('curvature') //曲率 数值越大 弯曲程度越小
            .linkCurveRotation('rotation') // 链接旋转方向
            .linkDirectionalParticles(2)
            .graphData(gData);

7.Auto-colored nodes/links

const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .nodeAutoColorBy('group') //节点根据group着色
            .linkAutoColorBy(d => gData.nodes[d.source].group) //链接根据group着色
            .linkOpacity(0.5) //链接透明度
            .graphData(gData);

 效果:

8.Text as nodes(文本作为节点)

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .jsonUrl('../datasets/miserables.json')
            .nodeAutoColorBy('group')
            .nodeThreeObject(node => {
                const sprite = new SpriteText(node.id);
                // sprite.material.depthWrite = false; // make sprite background transparent
                sprite.color = node.color;
                sprite.textHeight = 8;
                return sprite;
            });

        // Spread nodes a little wider
        Graph.d3Force('charge').strength(-120); //设定引力,是排斥还是吸引

效果:

 9.Images as nodes (图像作为节点)

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .nodeThreeObject(({ img }) => {
                const imgTexture = new THREE.TextureLoader().load(`./imgs/${img}`); //创建纹理贴图
                const material = new THREE.SpriteMaterial({ map: imgTexture });
                const sprite = new THREE.Sprite(material);
                sprite.scale.set(12, 12);
                return sprite;
            })
            .graphData(gData);

效果:

 10.HTML in nodes

        const Graph = ForceGraph3D({
            extraRenderers: [new THREE.CSS2DRenderer()]
        })(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeAutoColorBy('group')
            .nodeThreeObject(node => {
                const nodeEl = document.createElement('div');
                nodeEl.textContent = node.id;
                nodeEl.style.color = node.color;
                nodeEl.className = 'node-label';
                return new THREE.CSS2DObject(nodeEl);
            })
            .nodeThreeObjectExtend(true); //节点对象访问器函数、属性或布尔值,用于确定在使用自定义nodeThreeObject(false)时是替换默认节点还是扩展它(true)

 效果:(nodeThreeObjectExtend分别是true false)

 

 11.Custom node geometriesxiao

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .nodeThreeObject(
                ({ id }) =>
                    new THREE.Mesh(
                        [
                            // 方块
                            new THREE.BoxGeometry(
                                Math.random() * 20,
                                Math.random() * 20,
                                Math.random() * 20
                            ),
                            // 锥体
                            new THREE.ConeGeometry(Math.random() * 10, Math.random() * 20),
                            // 圆柱
                            new THREE.CylinderGeometry(
                                Math.random() * 10,
                                Math.random() * 10,
                                Math.random() * 20
                            ),
                            // 十二面体
                            new THREE.DodecahedronGeometry(Math.random() * 10),
                            // 球体
                            new THREE.SphereGeometry(Math.random() * 10),
                            // 圆环
                            new THREE.TorusGeometry(Math.random() * 10, Math.random() * 2),
                            // 环面扭结
                            new THREE.TorusKnotGeometry(Math.random() * 10, Math.random() * 2)
                        ][id % 7],
                        new THREE.MeshLambertMaterial({
                            color: Math.round(Math.random() * Math.pow(2, 24)),
                            transparent: true,
                            opacity: 0.75
                        })
                    )
            )
            .graphData(gData);

效果:

 

 12.Gradient Links(渐变链接)

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .nodeColor(node => nodeColorScale(node.id)) //id=0,color=c1;id=1,color=c2....id=4,color=c1...
            .linkThreeObject(link => {
                const colors = new Float32Array(
                    [].concat(
                        ...[link.source, link.target]
                            .map(nodeColorScale)
                            .map(d3.color)
                            .map(({ r, g, b }) => [r, g, b].map(v => v / 255))
                    )
                );

                const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors });
                const geometry = new THREE.BufferGeometry();
                geometry.setAttribute(
                    'position',
                    new THREE.BufferAttribute(new Float32Array(2 * 3), 3) //三个为一组 作为坐标
                );
                geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); //三个为一组 作为颜色

                return new THREE.Line(geometry, material);
            })
            .graphData(gData);

效果:

13.Text in Links

         const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeLabel('id')
            .nodeAutoColorBy('group')
            .linkThreeObjectExtend(true)
            .linkThreeObject(link => {
                // extend link with text sprite
                const sprite = new SpriteText(`${link.source} > ${link.target}`);
                sprite.color = 'lightgrey';
                sprite.textHeight = 1.5;
                return sprite;
            })
            .linkPositionUpdate((sprite, { start, end }) => {
                const middlePos = Object.assign(
                    ...['x', 'y', 'z'].map(c => ({
                        [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point
                    }))
                );
                // Position sprite
                Object.assign(sprite.position, middlePos);
            });

 效果:

14.Orbit controls && Fly controls

const Graph = ForceGraph3D({ controlType: 'orbit' })(document.getElementById('3d-graph'))
            .jsonUrl('../datasets/miserables.json')
            .nodeLabel('id')
            .nodeAutoColorBy('group');

const Graph = ForceGraph3D({ controlType: 'fly' })(document.getElementById('3d-graph'))
            .jsonUrl('../datasets/miserables.json')
            .nodeLabel('id')
            .nodeAutoColorBy('group');

设置threejs控制器的类型,效果相当于:

this.controls = new OrbitControls(this.camera, this.renderer.domElement);

this.controls = new TrackballControls(this.camera, this.renderer.domElement);

15.Camera automatic orbitting

        const distance = 1400;
        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .enableNodeDrag(false) //节点拖拽
            .enableNavigationControls(false) //拖拽
            .showNavInfo(false)
            .cameraPosition({ z: distance })
            .graphData(gData);

        // camera orbit
        // 每10ms更新一下相机的位置
        let angle = 0;
        setInterval(() => {
            Graph.cameraPosition({
                x: distance * Math.sin(angle),
                z: distance * Math.cos(angle)
            });
            angle += Math.PI / 300;
        }, 10);

相当于:camera.position.set(position); 

16.Click to focus on node

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeLabel('id')
            .nodeAutoColorBy('group')
            //点击事件
            .onNodeClick(node => {
                // Aim at node from outside it
                const distance = 40;
                const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);

                Graph.cameraPosition(
                    { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position
                    node, // lookAt ({ x, y, z })
                    3000 // ms transition duration
                );
            });

17.Click to expand/collapse nodes

        const Graph = ForceGraph3D()(elem)
            .graphData(getPrunedTree())
            .linkDirectionalParticles(2)
            //子节点折叠 red;子节点展开 yellow;无子节点 green;
            .nodeColor(node =>
                !node.childLinks.length ? 'green' : node.collapsed ? 'red' : 'yellow'
            )
            .onNodeHover(
                node => (elem.style.cursor = node && node.childLinks.length ? 'pointer' : null)
            )
            //点击事件调用getPrunedTree()更新gdata
            .onNodeClick(node => {
                if (node.childLinks.length) {
                    node.collapsed = !node.collapsed; // toggle collapse state
                    Graph.graphData(getPrunedTree());
                }
            });

18.Fix nodes after dragging

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeLabel('id')
            .nodeAutoColorBy('group')
            .onNodeDragEnd(node => {
                node.fx = node.x;
                node.fy = node.y;
                node.fz = node.z;
            });

拖动节点后,该节点位置不变。

19.Fit graph to canvas

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .cooldownTicks(100) //冷却时间 参数为时间
            .graphData(gData);

        // fit to canvas when engine stops
        Graph.onEngineStop(() => Graph.zoomToFit(400));

渲染完成后,会适应画布大小。

20.Highlight nodes/links

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            //node颜色
            .nodeColor(node =>
                highlightNodes.has(node)
                    ? node === hoverNode
                        ? 'rgb(255,0,0,1)'
                        : 'rgba(255,160,0,0.8)'
                    : 'rgba(0,255,255,0.6)'
            )
            .linkWidth(link => (highlightLinks.has(link) ? 4 : 1))
            .linkDirectionalParticles(link => (highlightLinks.has(link) ? 4 : 0))
            .linkDirectionalParticleWidth(4)
            .onNodeHover(node => {
                // no state change
                if ((!node && !highlightNodes.size) || (node && hoverNode === node)) return;

                highlightNodes.clear();
                highlightLinks.clear();
                if (node) {
                    highlightNodes.add(node);
                    node.neighbors.forEach(neighbor => highlightNodes.add(neighbor));
                    node.links.forEach(link => highlightLinks.add(link));
                }

                hoverNode = node || null;

                updateHighlight();
            })
            .onLinkHover(link => {
                highlightNodes.clear();
                highlightLinks.clear();

                if (link) {
                    highlightLinks.add(link);
                    highlightNodes.add(link.source);
                    highlightNodes.add(link.target);
                }
                updateHighlight();
            });

效果: 

 21.Multiple Node Selection

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .graphData(gData)
            .nodeRelSize(9) //节点大小
            .nodeColor(node => (selectedNodes.has(node) ? 'yellow' : 'grey'))
            .onNodeClick((node, event) => {
                if (event.ctrlKey || event.shiftKey || event.altKey) {
                    // multi-selection
                    selectedNodes.has(node) ? selectedNodes.delete(node) : selectedNodes.add(node);
                } else {
                    // single-selection
                    const untoggle = selectedNodes.has(node) && selectedNodes.size === 1;
                    selectedNodes.clear();
                    !untoggle && selectedNodes.add(node);
                }

                Graph.nodeColor(Graph.nodeColor()); // update color of selected nodes
            })
            .onNodeDrag((node, translate) => {
                if (selectedNodes.has(node)) {
                    // moving a selected node
                    [...selectedNodes]
                        .filter(selNode => selNode !== node) // don't touch node being dragged
                        .forEach(node =>
                            ['x', 'y', 'z'].forEach(
                                coord => (node[`f${coord}`] = node[coord] + translate[coord])
                            )
                        ); // translate other nodes by same amount
                }
            })
            .onNodeDragEnd(node => {
                if (selectedNodes.has(node)) {
                    // finished moving a selected node
                    [...selectedNodes]
                        .filter(selNode => selNode !== node) // don't touch node being dragged
                        .forEach(node =>
                            ['x', 'y', 'z'].forEach(coord => (node[`f${coord}`] = undefined))
                        ); // unfix controlled nodes
                }
            });

效果:按住alt 、 shift、alt均可以进行多选。

22.Dynamic data changes

        const Graph = ForceGraph3D()(elem)
            .enableNodeDrag(false)
            .onNodeClick(removeNode)
            .graphData(initData);

        //定时添加节点,并连接dst为已有的随机节点
        setInterval(() => {
            const { nodes, links } = Graph.graphData();
            const id = nodes.length;
            Graph.graphData({
                nodes: [...nodes, { id }],
                links: [...links, { source: id, target: Math.round(Math.random() * (id - 1)) }]
            });
        }, 1000);

        //
        function removeNode(node) {
            let { nodes, links } = Graph.graphData();
            links = links.filter(l => l.source !== node && l.target !== node); // Remove links attached to node
            nodes.splice(node.id, 1); // Remove node 从node.id开始删除1个元素
            //修正其他节点的id
            nodes.forEach((n, idx) => {
                n.id = idx;
            }); // Reset node ids to array index
            Graph.graphData({ nodes, links });
        }

23.Node collision detection(节点碰撞检测)

        const N = 50;
        const nodes = [...Array(N).keys()].map(() => ({
            // Initial velocity in random direction
            // 随机方向上的初始速度
            vx: Math.random(),
            vy: Math.random(),
            vz: Math.random()
        }));

        const Graph = ForceGraph3D()(document.getElementById('3d-graph'));

        Graph.cooldownTime(Infinity)
            .d3AlphaDecay(0) //alpha 衰减率
            .d3VelocityDecay(0) //默认为 0.4,较低的衰减系数可以使得迭代次数更多,其布局结果也会更理性,但是可能会引起数值不稳定从而导致震荡。

            // Deactivate existing forces
            .d3Force('center', null) //centering作用力可以使得节点布局开之后围绕某个中心
            .d3Force('charge', null) //作用力应用在所用的节点之间

            // Add collision and bounding box forces
            .d3Force('collide', d3.forceCollide(Graph.nodeRelSize()))
            .d3Force('box', () => {
                const CUBE_HALF_SIDE = Graph.nodeRelSize() * N * 0.5;

                nodes.forEach(node => {
                    const x = node.x || 0,
                        y = node.y || 0,
                        z = node.z || 0;

                    // bounce on box walls
                    if (Math.abs(x) > CUBE_HALF_SIDE) {
                        node.vx *= -1;
                    }
                    if (Math.abs(y) > CUBE_HALF_SIDE) {
                        node.vy *= -1;
                    }
                    if (Math.abs(z) > CUBE_HALF_SIDE) {
                        node.vz *= -1;
                    }
                });
            })

            // Add nodes
            .graphData({ nodes, links: [] });

24.暂停、恢复动画

        const distance = 1400;

        let isRotationActive = true;
        const Graph = ForceGraph3D()(document.getElementById('3d-graph'))
            .enableNodeDrag(false)
            .enableNavigationControls(false)
            .showNavInfo(true)
            .cameraPosition({ z: distance })
            .graphData(gData);
        // camera orbit
        let angle = 0;

        //使得物体旋转
        setInterval(() => {
            if (isRotationActive) {
                Graph.cameraPosition({
                    x: distance * Math.sin(angle),
                    z: distance * Math.cos(angle)
                });
                angle += Math.PI / 300;
            }
        }, 10);

        document.getElementById('rotationToggle').addEventListener('click', event => {
            isRotationActive = !isRotationActive;
            event.target.innerHTML = `${isRotationActive ? 'Pause' : 'Resume'} Rotation`;
        });

        let isAnimationActive = true;
        document.getElementById('animationToggle').addEventListener('click', event => {
            isAnimationActive ? Graph.pauseAnimation() : Graph.resumeAnimation();

            isAnimationActive = !isAnimationActive;
            event.target.innerHTML = `${isAnimationActive ? 'Pause' : 'Resume'} Animation`;
        });

初学结束~~~Fighting!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐