专业的编程技术博客社区

网站首页 > 博客文章 正文

d3力导向图一秒实现知识图谱

baijin 2024-11-25 09:38:52 博客文章 2 ℃ 0 评论

创建vue项目和依赖

首先创建一个vue的单页应用,为了能简便的设置项目样式,引入了bootstrap,项目使用的力向导图,主要依赖于d3.js,添加d3的依赖引用。

npm create vue@latest
npm install bootstrap
npm install d3

引入bootstrap依赖后,需要把对应的css路径设置到main.js上

import 'bootstrap/dist/css/bootstrap.css'

d3的不同版本的脚本使用有一定区别,当前版本是7.8.5

准备数据

需要准备一个节点数组和一个链接的边数组

nodes中的id是唯一标记,name是节点需要显示的名称

links中的source是线条开始的节点,其中target是线条结束的节点,name是线条上显示的名称,id是线条的唯一id

let nodes=[
    {"id":1,"name":"a"},
    {"id":2,"name":"b"},
    {"id":3,"name":"c"},
    {"id":4,"name":"d"},
    {"id":5,"name":"e"},
    {"id":6,"name":"f"}
  ]

let links=[
    {"id":1,"source":1,"target":2,"name":"link1"},
    {"id":2,"source":1,"target":3,"name":"link2"},
    {"id":3,"source":1,"target":4,"name":"link3"},
    {"id":4,"source":1,"target":5,"name":"link4"},
    {"id":5,"source":1,"target":6,"name":"link5"},
    {"id":6,"source":2,"target":6,"name":"link6"}
  ]

d3在项目中的实践应用

创建页面svg元素

在页面上写一个svg,注意必须包含id,后面会用到

<svg id="svg" style="width:100%;height:900"></svg>

准备d3的配色方案

获取d3的配色方案,注意NodeLabels是一个数组,可以根据自己的节点类型进行设计

var NodeLabels = ['A', 'B', 'C', 'D', "E", "F", "G"]
var color = d3.scaleOrdinal().domain(NodeLabels).range(d3.schemeTableau10)

初始化布局

布局中需要使用nodes和links数组,注意,最后一行的ticked方法还没有写,后面写

let simulation=d3.forceSimulation(nodes)
  .force('charge', d3.forceManyBody().strength(-3000))
  .force('center', d3.forceCenter(width / 2, height / 2))
  .force('x', d3.forceX(width / 2).strength(1))
  .force('y', d3.forceY(height / 2).strength(1))
  .force('link', d3.forceLink(links).id(function (d) { return d.id }).distance(0))
  .on('tick', ticked)

获取svg容器

其中width是svg的宽,height是svg的高。如果设定的固定高度,可以直接写死,

let svg = d3.select('#svg').attr('width', width).attr('height', height)
let container = svg.append('g')

如果是填充的高度/宽度可以通过clientWidth/clientHeight获取具体的值

let width=document.getElementById("svg").clientWidth
let height=document.getElementById("svg").clientHeight

设置nodes节点

通过下面的代码,会对nodes数组中的每个对象创建一个节点。节点的类型是一个圆(circle),半径是15,通过nodes对象的名称设置不同的颜色。

let node = container.append('g').attr('class', 'nodes')
  .selectAll('g')
  .data(nodes)
  .enter()
  .append('circle')
  .attr("id",d=>d.id)
  .attr('r', 15)
  .attr('fill', function (d) { return color(d.name) })

有了节点后,就可以设置 ticked方法了

function ticked () {
  node.attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')'
  })
}

到这就可以运行起来看效果了,到这所有的节点就能显示出来了

设置连接线的边

看到节点后,下面记录设置连接线

设置链接线,用到了links数组,设置的线颜色是红色,线宽度是1

let link = container.append('g').attr('class', 'links')
.selectAll('line')
.data(links)
.enter()
.append('line')
.attr('stroke', d=>{
  return "red"
})
.attr('stroke-width', '1px')

修改上面的ticked方法,增加设置线在svg上的位置

function ticked () {
  node.attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')'
  })

  link.attr('x1', function (d) { return (d.source.x) })
  .attr('y1', function (d) { return (d.source.y) })
  .attr('x2', function (d) { return (d.target.x) })
  .attr('y2', function (d) { return (d.target.y) })

}

再次运行,就能看到节点和连接线了

设置节点文字

在节点上显示对应节点的名字

虽然显示了各种节点,但是不知道具体那个节点什么含义,就要使用到节点的名称(name属性),

通过.text属性设置具体的显示字段,通过fill设置字体颜色,通过x,y分别设置文字的位置

let nodeword = container.append('g').attr('class', 'labelNodes')
  .selectAll('text')
  .data(nodes)
  .enter()
  .append('text')
  .text(function (d, i) { return d.name })
  .style('fill', '#555')
  .style('font-family', 'Arial')
  .attr("text-anchor", "middle")
  .style('font-size', 16)
  .style('pointer-events', 'none')
  .attr('x', function (d) {
      return d.x;
  })
  .attr('y', d => d.y)

再次修改ticked方法,显示节点的名称

把下面的代码放入ticked方法中

nodeword.attr('transform', function (d) {
  return 'translate(' + d.x + ',' + d.y + ')'
})

再次运行的效果图,如下图

设置连接线文字

如果连接线的边也有具体的含义,也可以设置边的文字描述

使用text属性设置具体需要显示的字段

let linkword = container.append('g').attr('class', 'labelText')
        .selectAll('text')
        .data(links)
        .enter()
        .append('text')
        .text(function (d, i) { return d.name })
        .style('fill', '#555')
        .style('font-family', 'Arial')
        .attr("text-anchor", "middle")
        .style('font-size', 10)
        .style('pointer-events', 'none')

再次修改ticked方法,设置连接线的边的文字位置

把下面的代码,直接放到ticked方法中即可

linkword.attr('transform', d => {
let x = Math.min(d.source.x, d.target.x) + Math.abs(d.source.x - d.target.x) / 2
let y = Math.min(d.source.y, d.target.y) + Math.abs(d.source.y - d.target.y) / 2 - 1
let tanA = Math.abs(d.source.y - d.target.y) / Math.abs(d.source.x - d.target.x)
let angle = Math.atan(tanA) / Math.PI * 180
if (d.source.x > d.target.x) {
    if (d.source.y <= d.target.y) {
        angle = -angle
    }
} else if (d.source.y > d.target.y) {
    angle = -angle
}
return 'translate(' + x + ',' + y + ')' + 'rotate(' + angle + ')'
})

重新运行的效果如下图:

设置svg的缩放

其中缩放范围是0.1到4倍大小。注意d3.js的5版本和7版本的使用方式不一样

svg.call(
  d3.zoom()
    .scaleExtent([.1, 4])
    .on('zoom', function () { container.attr('transform', d3.zoomTransform(this)) })
)

设置svg图的节点拖拽功能

默认情况下所有的节点是不能拖动的,如果项节点可以随意拖动,可以设置drag方法,这样节点就可以随意拖动了

function dragstarted (event) {
  event.sourceEvent.stopPropagation()
  if (!event.active) simulation.alphaTarget(0.3).restart()
  event.subject.fx = event.subject.x
  event.subject.fy = event.subject.y
}

function dragged (event) {
  event.subject.fx = event.x
  event.subject.fy = event.y
}

function dragended (event) {
  if (!event.active) simulation.alphaTarget(0)
  event.subject.fx = null
  event.subject.fy = null
}

node.call(
    d3.drag()
      .on('start', dragstarted)
      .on('drag', dragged)
      .on('end', dragended)
  )

删除svg上的所有的节点

如果在当前svg上需要进行多次操作,可以在每次开始前删除所有的svg元素

d3.select('svg').selectAll('*').remove();

效果图


写在最后

现在网上有很多d3的版本5的代码,但是在实际使用的时候总会出现各种莫名其妙的错误。

由于vue已经设计到vue3了,很多以前的vue项目都需要node17及以下版本才能使用。如果使用最新的nodejs则会出现各种莫名其妙的错误,根本解决不完

按照官方的要求,重新写了一份。花费了很多天的时间,也走了很多的弯路,最终结果还是好的。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表