d3js 各种力的仿真

Posted 随遇丿而安

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了d3js 各种力的仿真相关的知识,希望对你有一定的参考价值。

d3js 各种力的仿真

d3js 是一种可以自由组合图形的库

我的理解,简单图形,比如柱形图,饼状图,条形图,要求不高可以直接用 echarts,自由度需要高些的使用 d3js,需要频繁交互的使用 pixi

偶然发现 d3js 可以进行各种力的模拟,学习后进行分析,希望大家能点个赞,让我有更多动力。点赞过十,下星期分享一篇 d3js 入门教程

效果如下

1. 创建数据

首先创建两组 JSON 数据,这里我使用的是网上的数据。一组数据记录了每个点的信息,另一组数据记录了连接信息。

// 结点信息的文件 node_data.json
[
    "x": 30.5, "y": 100.7, "r": 4,
    ...
]
    
// 连接信息的文件 edge_data.json
[
    "source": 0, "target": 98,
    ...
]

2. 代码结构

力的模拟分为四个步骤

  1. 加载数据
  2. 创建仿真变量
  3. 绑定数据并画出图形
  4. 在帧回调函数中更新图形
// step 1
const nodes = await d3.json("/force/node_data.json");

// step 2
const sim = d3.forceSimulation(nodes)
	.force(\'力的名字\', ...)
    .on(\'tick\', tick_function);

// step3
const node = svg.selectAll("circle")
            .data(nodes)
            .enter()
            .append(\'circle\');

// step4
tick_function()
    // 更新图形

3. 位置x和y的力

特定位置x,y有中拉力,将其他图形拉向自己,比如让特定位置为画框中心

const sim = d3.forceSimulation(nodes)
    // 指定位置的拉力
    .force(\'x\', d3.forceX(width/2))
    .force(\'y\', d3.forceY(height/2));

可以设置力的大小,力如果越大,那么拉力越强,越小越弱

const sim = d3.forceSimulation(nodes)
    // 指定位置的拉力
    .force(\'x\', d3.forceX(width/2).strength(0.06))
    .force(\'y\', d3.forceY(height/2).strength(0.06))

4. 碰撞力

如果不想让所有图形重合,那么就需要使用碰撞力,碰撞力以坐标为圆心,设置碰撞半径,碰撞半径内不会发生重合。比如让位置力和碰撞力结合

const sim = d3.forceSimulation(nodes)
    // 指定位置的拉力
    .force(\'x\', d3.forceX(width/2).strength(0.06))
    .force(\'y\', d3.forceY(height/2).strength(0.06))
    // 碰撞力, radius(碰撞半径)
    .force(\'collide\', d3.forceCollide().radius(10))

我们会发现有一些图形会有重合部分,这是因为我们设置了固定半径,有些图形的半径要大于固定半径,因此我们需要动态设置半径

const sim = d3.forceSimulation(nodes)
    // 指定位置的拉力
    .force(\'x\', d3.forceX(width/2).strength(0.06))
    .force(\'y\', d3.forceY(height/2).strength(0.06))
    // 碰撞力, radius(碰撞半径)
    .force(\'collide\', d3.forceCollide().radius(d=>d.r+1))

5. 原子力

这里是我自己的叫法,实际上这个力叫 many-body force,表示每个图形自身存在对其他图形的力,这个力如果是正数,代表了吸引力,如果这个力是负数,代表了排斥力

const sim = d3.forceSimulation(nodes)
    // 原子力,正数吸引力
    .force(\'charge\', d3.forceManyBody().strength(7))

看下排除力

const sim = d3.forceSimulation(nodes)
    // 原子力,正数吸引力
    .force(\'charge\', d3.forceManyBody().strength(-7))

当原子力为吸引力时,所有图形聚合起来了,不好看,加上碰撞力再看看效果

const sim = d3.forceSimulation(nodes)
    // 碰撞力, radius(碰撞半径)
    .force(\'collide\', d3.forceCollide().radius(d=>d.r+1))
    // 原子力,正数吸引力
    .force(\'charge\', d3.forceManyBody().strength(7))

看起来效果和位置力类似,但是核心内容完全不同,原子力代表每个图形自身都有对其他图形的力,而位置力是一个点对其他图形的吸引力

6. 链接力

表示连接线产生的力,类似于弹簧,远了就拉回来,近了就推出去

const sim = d3.forceSimulation(nodes)
    // 链接力
    .force(\'link\', d3.forceLink(edges))

还是给链接力加个碰撞力,再加上刚刚了解的原子力看看效果

const sim = d3.forceSimulation(nodes)
    .force(\'collide\', d3.forceCollide().radius(d=>d.r+1))
    // 原子力
    .force(\'charge\', d3.forceManyBody().strength(-7))
    // 链接力
    .force(\'link\', d3.forceLink(edges))

有点鲜花盛开的感觉是不

7. 径向力

我们可以定义一个圆,这个圆上的每一点都会有个从圆心指向该点的力,会让所有图形集中再圆上

const sim = d3.forceSimulation(nodes)
	// 径向力
    .force(\'radial\', d3.forceRadial(240, width/2, height/2))

径向力让所有图形在一个圆上并且图形直接由重合,不太美观,加其他力试试

const sim = d3.forceSimulation(nodes)
   	// 碰撞力
    .force(\'collide\', d3.forceCollide().radius(d=>d.r+1))
    // 原子力
    .force(\'charge\', d3.forceManyBody().strength(-7))
    // 径向力
    // .force(\'radial\', d3.forceRadial(240, width/2, height/2))
    .force(\'radial\', d3.forceRadial(d => d.r+200, width/2, height/2))

将径向力半径改为动态生成,加入碰撞力和原子力后是不是就好看多了

8. 中心力

中心力也是定义了一个圆,这个力有让图形移动到该圆内的趋势,这个力要与其他力配合使用

const sim = d3.forceSimulation(nodes)
    // 碰撞力
    .force(\'collide\', d3.forceCollide().radius(d=>d.r+1))
    // 原子力
    .force(\'charge\', d3.forceManyBody().strength(7))
    // 中心力,规划
    .force(\'center\', d3.forceCenter(centerForce.x, centerForce.y))

9. 代码解析

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="/lib/d3.v7.min.js"></script>
<script type="module">
    const width=1000, height = 500;
    const svg = d3.select("body")
        .append("svg").attr(\'width\', width).attr(\'height\', height);
    
    // 选取颜色色系
    const color = d3.scaleOrdinal(d3.schemeTableau10)

    async function load()
        // 加载数据
        const nodes = await d3.json("/force/node_data.json");
        const edges = await d3.json("/force/edge_data.json");
        
        // 绑定数据并形成圆
        const node = svg.selectAll("circle")
            .data(nodes)
            .enter()
            .append(\'circle\')
            .attr("cx", d=>d.x)
            .attr("cy", d=>d.y)
            .attr("r", d=>d.r)
            .attr("fill", (d, i)=>color(i));
        
		// 绑定数据并形成线
        const lines = svg.selectAll(\'lines\')
            .data(edges)
            .enter()
            .append(\'line\')
            .attr(\'x1\',d=>nodes[d.source].x)
            .attr(\'x2\',d=>nodes[d.target].x)
            .attr(\'y1\',d=>nodes[d.source].y)
            .attr(\'y2\',d=>nodes[d.target].y)
            .attr(\'stroke\', "#5d5d66")
            .attr(\'stroke-width\', 2)

        const centerForce = 
            x: 700,
            y: 250
        

        // 创建中心力的圆
        svg.append(\'circle\')
            .attr(\'cx\', centerForce.x)
            .attr(\'cy\', centerForce.y)
            .attr(\'r\', 100)
            .attr(\'stroke\', \'#6b6f80\')
            .attr(\'stroke-width\', 3)
            .attr(\'fill\', "#00000000")

        // 力的模拟
        const sim = d3.forceSimulation(nodes)
            // 指定位置的拉力
            // .force(\'x\', d3.forceX(width/2))
            // .force(\'y\', d3.forceY(height/2))
            // .force(\'x\', d3.forceX(width/2).strength(0.06))
            // .force(\'y\', d3.forceY(height/2).strength(0.06))
            // 碰撞力
            // .force(\'collide\', d3.forceCollide().radius(10))
            .force(\'collide\', d3.forceCollide().radius(d=>d.r+1))
            // 原子力
            .force(\'charge\', d3.forceManyBody().strength(7))
            // 链接力
            // .force(\'link\', d3.forceLink(edges))
            // 径向力
            // .force(\'radial\', d3.forceRadial(240, width/2, height/2))
            // .force(\'radial\', d3.forceRadial(d => d.r+200, width/2, height/2))
            // 中心力,规划
            .force(\'center\', d3.forceCenter(centerForce.x, centerForce.y))
        // .stop()
        //  .tick(100);


        sim.on("tick", () => 
            // sim 直接改变了
            node.attr("cx", function(d) return d.x;)
                .attr("cy", function(d) return d.y;);


            lines.attr(\'x1\',d=>d.source.x)
                .attr(\'y1\',d=>d.source.y)
                .attr(\'x2\',d=>d.target.x)
                .attr(\'y2\',d=>d.target.y);
        )
    
    load()
</script>
</body>
</html>

如果觉得不错的话,给个赞,有赞才能有动力呀,过十赞下星期写 d3js 入门教程

希望读者在看完后能提出意见, 点个赞, 鼓励一下, 我们一起进步. 加油 !!

以上是关于d3js 各种力的仿真的主要内容,如果未能解决你的问题,请参考以下文章

论假如有一台无限算力的主机

接触力的计算

vbscript 各种自定义代码片段 - 有关详细信息,请参阅注释

D3js:如何通过鼠标单击获取纬度/经度地理坐标?

小程序各种功能代码片段整理---持续更新

threejs+d3js 绘制3D动态数据图