d3js 是一种可以自由组合图形的库
我的理解,简单图形,比如柱形图,饼状图,条形图,要求不高可以直接用 echarts,自由度需要高些的使用 d3js,需要频繁交互的使用 pixi
偶然发现 d3js 可以进行各种力的模拟,学习后进行分析,希望大家能点个赞,让我有更多动力。点赞过十,下星期分享一篇 d3js 入门教程
效果如下
首先创建两组 JSON 数据,这里我使用的是网上的数据。一组数据记录了每个点的信息,另一组数据记录了连接信息。
// 结点信息的文件 node_data.json [ {"x": 30.5, "y": 100.7, "r": 4}, ... ] // 连接信息的文件 edge_data.json [ {"source": 0, "target": 98}, ... ]
力的模拟分为四个步骤
// 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(){ // 更新图形 }
特定位置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))
如果不想让所有图形重合,那么就需要使用碰撞力,碰撞力以坐标为圆心,设置碰撞半径,碰撞半径内不会发生重合。比如让位置力和碰撞力结合
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))
这里是我自己的叫法,实际上这个力叫 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))
看起来效果和位置力类似,但是核心内容完全不同,原子力代表每个图形自身都有对其他图形的力,而位置力是一个点对其他图形的吸引力
表示连接线产生的力,类似于弹簧,远了就拉回来,近了就推出去
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))
有点鲜花盛开的感觉是不
我们可以定义一个圆,这个圆上的每一点都会有个从圆心指向该点的力,会让所有图形集中再圆上
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))
将径向力半径改为动态生成,加入碰撞力和原子力后是不是就好看多了
中心力也是定义了一个圆,这个力有让图形移动到该圆内的趋势,这个力要与其他力配合使用
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))
<!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 入门教程