携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
引入D3模块
- 引入整个D3模块。
<!-- D3模块 -->
<script src="https://d3js.org/d3.v7.min.js"></script>
数据
// 节点集
// radius 节点半径
const nodes = [
{ name: '重庆市', radius: 4 },
{ name: '北碚区', radius: 3 },
{ name: '渝北区', radius: 3 },
{ name: '江北区', radius: 3 },
{ name: '巴南区', radius: 3 },
{ name: '沙坪坝区', radius: 3 },
{ name: '大渡口', radius: 3 },
{ name: '万州区', radius: 3 },
{ name: '天府镇', radius: 2 },
{ name: '水土镇', radius: 2 },
{ name: '蔡家镇', radius: 2 },
{ name: '土场镇', radius: 2 },
{ name: '两江名居', radius: 1 },
{ name: '上城中央', radius: 1 },
{ name: '灯塔', radius: 1 }
]
// 边集
// value 控制线的长短
const edges = [
{ source: 0, target: 1, value: 2 },
{ source: 0, target: 2, value: 2 },
{ source: 0, target: 3, value: 2 },
{ source: 0, target: 4, value: 2 },
{ source: 0, target: 5, value: 2 },
{ source: 0, target: 6, value: 2 },
{ source: 0, target: 7, value: 2 },
{ source: 2, target: 8, value: 1.5 },
{ source: 2, target: 9, value: 1.5 },
{ source: 2, target: 10, value: 1.5 },
{ source: 2, target: 11, value: 1.5 },
{ source: 11, target: 12, value: 1 },
{ source: 11, target: 13, value: 1 },
{ source: 11, target: 14, value: 1 }
]
- 边集中的
source
和target
对应节点集中的下标。 - 当节点集存在
id
属性时就是对应id
属性。
添加画布
- 初始化画布。
// 画布
const width = 1000
const height = 1000
const svg = d3
.select('.d3Chart')
.append('svg')
.attr('width', width)
.attr('height', height)
.style('background-color', '#1a3055')
// 图
const chart = svg.append('g')
比例尺和配置信息
// 创建颜色比例尺
const colorScale = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, nodes.length + 1))
- 颜色比例尺,参数相同返回相同颜色。
const force = d3
.forceSimulation()
.force('link', d3.forceLink())
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width / 2, height / 2))
.forceSimulation()
创建一个力模拟。.force(name[, force])
如果指定了力,则为指定的名称分配力并返回此模拟。d3.forceLink()
适用每个链接的源和目标,链接力。d3.forceManyBody()
多体力。d3.forceCenter(x, y)
使用指定的x和y坐标创建新的定心力。如果未指定x和y,则默认为{0,0}。
- 使用
d3.forceSimulation()
创建力模拟函数。
const forceNodes = force.nodes(nodes).on('tick', ticked)
.nodes()
指定节点,将模拟的节点设置为指定的对象数组。.on('tick', fun)
监听迭代次数逐步执行模拟。每一次力的模拟都会执行这个方法。
- 加入节点数据,计算节点布局信息。
force
.force('link')
.links(edges)
.distance(function (d) {
//每一边的长度
return d.value * 150
})
.force('link')
如果未指定力,则返回具有指定名称的力函数。.links()
指定链接,设置与此力关联的链接数组,重新计算每个链接的距离和强度参数,并返回此力。.distance()
指定距离,则将距离访问器设置为指定的数字或函数,重新计算每个链接的距离访问器,并返回此力.
- 加入边集数据,生成关联线布局信息。
绘制节点和连线
const line = chart.append('g').selectAll().data(edges).enter().append('g')
- 创建连线绘制组,绑定数据。对每条线创建绘制组,放入连线和文本,后期方便处理。
// 绘制线
const links = line.append('line').attr('stroke', '#ccc').attr('stroke-width', 1)
const linksText = line
.append('text')
.text(function (d) {
return d.value
})
.attr('fill', '#ccc')
- 创建连线和连线文本。这里没有位置信息,因为力模拟会实时变化位置,位置信息都在
ticked()
函数中设置。
const nodesChart = chart
.append('g')
.selectAll()
.data(nodes)
.enter()
.append('g')
.attr('transform', function (d, i) {
var cirX = d.x
var cirY = d.y
return 'translate(' + cirX + ',' + cirY + ')'
})
- 创建节点绘制组。
nodesChart
.append('circle')
.attr('r', function (d, i) {
// 半径
return d.radius * 10
})
.attr('fill', function (d, i) {
return colorScale(d.name)
})
nodesChart
.append('text')
.attr('x', -20)
.attr('y', -5)
.attr('dy', 10)
.attr('font-size', 12)
.text(function (d) {
return d.name
})
.attr('fill', '#ccc')
.attr('pointer-events', 'none')
.style('user-select', 'none')
- 节点信息和文本信息在同一绘制组下,方便后期操作。
function ticked() {
links
.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
})
linksText
.attr('x', function (d) {
return (d.source.x + d.target.x) / 2
})
.attr('y', function (d) {
return (d.source.y + d.target.y) / 2
})
nodesChart.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')'
})
}
- 创建
ticked
函数,在内部设置图形的位置信息。
function ticked() {
links
.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
})
linksText
.attr('x', function (d) {
return (d.source.x + d.target.x) / 2
})
.attr('y', function (d) {
return (d.source.y + d.target.y) / 2
})
nodesChart.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')'
})
}
添加交互
- 上面关联过多、排斥力未设置,绘制的图形都在一起。添加拖拽交互。
nodesChart.call(d3.drag().on('start', started).on('drag', dragged).on('end', ended))
// d.fx 和 d.fy 表示固定坐标
function started(e, d) {
force.alphaTarget(0.8).restart() // 设置衰减系数
d.fx = d.x
d.fy = d.y
}
function dragged(e, d) {
d.fx = e.x
d.fy = e.y
}
function ended(e, d) {
force.alphaTarget(0).restart()
d.fx = null
d.fy = null
}
.alphaTarget(0.8)
对节点位置移动过程的模拟,数值越高移动越快,数值范围[0,1]。d.fx、d.fy
节点的固定,设置后节点会移动到对应位置。d3.drag()
创建一个拖动事件,然后绑定到元素上。
- 使用
d3.drag()
创建拖动事件。设置开始拖动时元素为当前位置并重置力函数的衰减系数,拖动中设置为当前鼠标位置,结束时重置固定位置和衰减系数。