D3.js实现拓扑图
最近写项目需要画出应用程序调用链的网路拓扑图,完全自己写需要花费些时间,那么首先想到的是echarts,但echarts的自定义写法写起来非常麻烦,而且它的文档都是基于配置说明的,对于自定义开发不太方便,尝试后果断放弃,改用D3.js,自己完全可控。
我们先看看效果
我把代码分享下,供和我一样刚接触D3的同学参考,不对的地方欢迎指正!
完整代码:
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="http://d3js.org/d3.v5.min.js">
</script>
<style>
body{
overflow: hidden;
}
#togo{
width: 800px;
height:500px;
border:1px solid #ccc;
user-select: none;
}
#togo text{
font-size:10px;/*和js里保持一致*/
fill:#1A2C3F;
text-anchor: middle;
}
#togo .node-other{
text-anchor: start;
}
#togo .health1{
stroke:#92E1A2;
}
#togo .health2{
stroke:orange;
}
#togo .health3{
stroke:red;
}
#togo #cloud,#togo #database{
fill:#ccc;
}
#togo .link{
stroke:#E4E8ED;
}
#togo .node-title{
font-size: 14px;
}
#togo .node-code circle{
fill:#3F86F5;
}
#togo .node-code text{
fill:#fff;
}
#togo .node-bg{
fill:#fff;
}
#togo .arrow{
fill:#E4E8ED;
}
</style>
<script src="data.js"></script>
</head>
<body>
<svg id="togo" width="800" height="500">
</svg>
<script src="togo.js"></script>
<script>
</script>
<script>
let t=new Togo('#togo',__options);
t.render();
</script>
</body>
</html>
JS:
const fontSize = 10;
const symbolSize = 40;
const padding = 10;
/*
* 调用 new Togo(svg,option).render();
* */
class Togo {
/**/
constructor(svg, option) {
this.data = option.data;
this.edges = option.edges;
this.svg = d3.select(svg);
}
//主渲染方法
render() {
this.scale = 1;
this.width = this.svg.attr('width');
this.height = this.svg.attr('height');
this.container = this.svg.append('g')
.attr('transform', 'scale(' + this.scale + ')');
this.initPosition();
this.initDefineSymbol();
this.initLink();
this.initNode();
this.initZoom();
}
//初始化节点位置
initPosition() {
let origin = [this.width / 2, this.height / 2];
let points = this.getVertices(origin, Math.min(this.width, this.height) * 0.3, this.data.length);
this.data.forEach((item, i) => {
item.x = points[i].x;
item.y = points[i].y;
})
}
//根据多边形获取定位点
getVertices(origin, r, n) {
if (typeof n !== 'number') return;
var ox = origin[0];
var oy = origin[1];
var angle = 360 / n;
var i = 0;
var points = [];
var tempAngle = 0;
while (i < n) {
tempAngle = (i * angle * Math.PI) / 180;
points.push({
x: ox + r * Math.sin(tempAngle),
y: oy + r * Math.cos(tempAngle),
});
i++;
}
return points;
}
//两点的中心点
getCenter(x1, y1, x2, y2) {
return [(x1 + x2) / 2, (y1 + y2) / 2]
}
//两点的距离
getDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
//两点角度
getAngle(x1, y1, x2, y2) {
var x = Math.abs(x1 - x2);
var y = Math.abs(y1 - y2);
var z = Math.sqrt(x * x + y * y);
return Math.round((Math.asin(y / z) / Math.PI * 180));
}
//初始化缩放器
initZoom() {
let self = this;
let zoom = d3.zoom()
.scaleExtent([0.7, 3])
.on('zoom', function () {
self.onZoom(this)
});
this.svg.call(zoom)
}
//初始化图标
initDefineSymbol() {
let defs=this.container.append('svg:defs');
//箭头
const marker = defs
.selectAll('marker')
.data(this.edges)
.enter()
.append('svg:marker')
.attr('id', (link, i) => 'marker-' + i)
.attr('markerUnits', 'userSpaceOnUse')
.attr('viewBox', '0 -5 10 10')
.attr('refX', symbolSize / 2 + padding)
.attr('refY', 0)
.attr('markerWidth', 14)
.attr('markerHeight', 14)
.attr('orient', 'auto')
.attr('stroke-width', 2)
.append('svg:path')
.attr('d', 'M2,0 L0,-3 L9,0 L0,3 M2,0 L0,-3')
.attr('class','arrow')
//数据库
let database =defs.append('g')
.attr('id','database')
.attr('transform','scale(0.042)');
database.append('path')
.attr('d','M512 800c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V640c0 88.37-200.58 160-448 160z')
database.append('path')
.attr('d','M512 608c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V448c0 88.37-200.58 160-448 160z') ;
database.append('path')
.attr('d','M512 416c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V256c0 88.37-200.58 160-448 160z') ;
database.append('path')
.attr('d','M64 224a448 160 0 1 0 896 0 448 160 0 1 0-896 0Z');
//云
let cloud=defs.append('g')
.attr('id','cloud')
.attr('transform','scale(0.042)')
.append('path')
.attr('d','M709.3 285.8C668.3 202.7 583 145.4 484 145.4c-132.6 0-241 102.8-250.4 233-97.5 27.8-168.5 113-168.5 213.8 0 118.9 98.8 216.6 223.4 223.4h418.9c138.7 0 251.3-118.8 251.3-265.3 0-141.2-110.3-256.2-249.4-264.5z')
}
//初始化链接线
initLink() {
this.drawLinkLine();
this.drawLinkText();
}
//初始化节点
initNode() {
var self = this;
//节点容器
this.nodes = this.container.selectAll(".node")
.data(this.data)
.enter()
.append("g")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(d3.drag()
.on("drag", function (d) {
self.onDrag(this, d)
})
)
.on('click', function () {
alert()
})
//节点背景默认覆盖层
this.nodes.append('circle')
.attr('r', symbolSize / 2 + padding)
.attr('class', 'node-bg');
//节点图标
this.drawNodeSymbol();
//节点标题
this.drawNodeTitle();
//节点其他说明
this.drawNodeOther();
this.drawNodeCode();
}
//画节点语言标识
drawNodeCode() {
this.nodeCodes = this.nodes.filter(item => item.type == 'app')
.append('g')
.attr('class','node-code')
.attr('transform', 'translate(' + -symbolSize / 2 + ',' + symbolSize / 3 + ')')
this.nodeCodes
.append('circle')
.attr('r', d => fontSize / 2 * d.code.length / 2 + 3)
this.nodeCodes
.append('text')
.attr('dy', fontSize / 2)
.text(item => item.code);
}
//画节点图标
drawNodeSymbol() {
//绘制节点
this.nodes.filter(item=>item.type=='app')
.append("circle")
.attr("r", symbolSize / 2)
.attr("fill", '#fff')
.attr('class', function (d) {
return 'health'+d.health;
})
.attr('stroke-width', '5px')
this.nodes.filter(item=>item.type=='database')
.append('use')
.attr('xlink:href','#database')
.attr('x',function () {
return -this.getBBox().width/2
})
.attr('y',function () {
return -this.getBBox().height/2
})
this.nodes.filter(item=>item.type=='cloud')
.append('use')
.attr('xlink:href','#cloud')
.attr('x',function () {
return -this.getBBox().width/2
})
.attr('y',function () {
return -this.getBBox().height/2
})
}
//画节点右侧信息
drawNodeOther() {
//如果是应用的时候
this.nodeOthers = this.nodes.filter(item => item.type == 'app')
.append("text")
.attr("x", symbolSize / 2 + padding)
.attr("y", -5)
.attr('class','node-other')
this.nodeOthers.append('tspan')
.text(d => d.time + 'ms');
this.nodeOthers.append('tspan')
.text(d => d.rpm + 'rpm')
.attr('x', symbolSize / 2 + padding)
.attr('dy', '1em');
this.nodeOthers.append('tspan')
.text(d => d.epm + 'epm')
.attr('x', symbolSize / 2 + padding)
.attr('dy', '1em')
}
//画节点标题
drawNodeTitle() {
//节点标题
this.nodes.append("text")
.attr('class','node-title')
.text(function (d) {
return d.name;
})
.attr("dy", symbolSize)
this.nodes.filter(item => item.type == 'app').append("text")
.text(function (d) {
return d.active + '/' + d.total;
})
.attr('dy', fontSize / 2)
.attr('class','node-call')
}
//画节点链接线
drawLinkLine() {
let data = this.data;
if (this.lineGroup) {
this.lineGroup.selectAll('.link')
.attr(
'd', link => genLinkPath(link),
)
} else {
this.lineGroup = this.container.append('g')
this.lineGroup.selectAll('.link')
.data(this.edges)
.enter()
.append('path')
.attr('class', 'link')
.attr(
'marker-end', (link, i) => 'url(#' + 'marker-' + i + ')'
).attr(
'd', link => genLinkPath(link),
).attr(
'id', (link, i) => 'link-' + i
)
.on('click', () => { alert() })
}
function genLinkPath(d) {
let sx = data[d.source].x;
let tx = data[d.target].x;
let sy = data[d.source].y;
let ty = data[d.target].y;
return 'M' + sx + ',' + sy + ' L' + tx + ',' + ty;
}
}
drawLinkText() {
let data = this.data;
let self = this;
if (this.lineTextGroup) {
this.lineTexts
.attr('transform', getTransform)
} else {
this.lineTextGroup = this.container.append('g')
this.lineTexts = this.lineTextGroup
.selectAll('.linetext')
.data(this.edges)
.enter()
.append('text')
.attr('dy', -2)
.attr('transform', getTransform)
.on('click', () => { alert() })
this.lineTexts
.append('tspan')
.text((d, i) => this.data[d.source].lineTime + 'ms,' + this.data[d.source].lineRpm + 'rpm');
this.lineTexts
.append('tspan')
.text((d, i) => this.data[d.source].lineProtocol)
.attr('dy', '1em')
.attr('dx', function () {
return -this.getBBox().width / 2
})
}
function getTransform(link) {
let s = data[link.source];
let t = data[link.target];
let p = self.getCenter(s.x, s.y, t.x, t.y);
let angle = self.getAngle(s.x, s.y, t.x, t.y);
if (s.x > t.x && s.y < t.y || s.x < t.x && s.y > t.y) {
angle = -angle
}
return 'translate(' + p[0] + ',' + p[1] + ') rotate(' + angle + ')'
}
}
update(d) {
this.drawLinkLine();
this.drawLinkText();
}
//拖拽方法
onDrag(ele, d) {
d.x = d3.event.x;
d.y = d3.event.y;
d3.select(ele)
.attr('transform', "translate(" + d3.event.x + "," + d3.event.y + ")")
this.update(d);
}
//缩放方法
onZoom(ele) {
var transform = d3.zoomTransform(ele);
this.scale = transform.k;
this.container.attr('transform', "translate(" + transform.x + "," + transform.y + ")scale(" + transform.k + ")")
}
}
数据:
let __options={
data:[{
type:'app',
name: 'monitor-web-server',
time: 30,
rpm: 40,
epm: 50,
active: 3,
total: 5,
code: 'java',
health: 1,
lineProtocol: 'http',
lineTime: 12,
lineRpm: 34,
}, {
type:'database',
name: 'Mysql',
time: 30,
rpm: 40,
epm: 50,
active: 3,
total: 5,
code: 'java',
health: 2,
lineProtocol: 'http',
lineTime: 12,
lineRpm: 34,
},
{
type:'app',
name: 'Redis',
time: 30,
rpm: 40,
epm: 50,
active: 3,
total: 5,
code: 'java',
health: 3,
lineProtocol: 'http',
lineTime: 12,
lineRpm: 34,
}, {
type:'cloud',
name: 'ES',
time: 30,
rpm: 40,
epm: 50,
active: 3,
total: 5,
code: 'java',
health: 1,
lineProtocol: 'http',
lineTime: 12,
lineRpm: 34,
value: 100
}
],
edges: [
{
source: 0,
target: 3,
}, {
source: 1,
target: 2,
}
, {
source: 1,
target: 3,
},
{
source: 0,
target: 1,
},
{
source: 0,
target: 2,
}
// {
// source: 3,
// target: 2,
// },
]
}D3.js实现拓扑图的更多相关文章
- D3.js学习(七)
上一节中我们学会了如何旋转x轴标签以及自定义标签内容,在这一节中,我们将接触动画(transition) 首先,我们要在页面上添加一个按钮,当我们点击这个按钮时,调用我们的动画.所以,我们还需要在原来 ...
- D3.js学习(一)
从今天开始我将和大家一起学习D3.js(Data-Driven Documents),由于国内关于D3的学习资料少之又少,所以我觉得很有必要把自己学习过程记录下来,供同学们参考,如果文章有有哪些表达有 ...
- D3.js学习记录
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- D3.js部署node环境开发
总结一段D3.js部署node环境的安装过程 准备阶段: 首先电脑上要安装node环境,这个阶段过滤掉,如果node环境都不会装,那就别玩基于node环境搞的其他东西了. 搭建环境: 我在自己的F:系 ...
- d3.js读书笔记-1
d3.js入门 d3入门 D3是一个强大的数据可视化工具,它是基于Javascript库的,用于创建数据可视化图形.在生成可视化图形的过程中,需要以下几步: 把数据加载到浏览器的内存空间: 把数据绑定 ...
- 【 D3.js 进阶系列 — 6.1 】 缩放的应用(Zoom)
缩放(Zoom)是另一种重要的可视化操作,主要是使用鼠标的滚轮进行. 1. zoom 的定义 缩放是由 d3.behavior.zoom() 定义的. var zoom = d3.behavior.z ...
- [资料搜集狂]D3.js数据可视化开发库
偶然看到一个强大的D3.js,存档之. D3.js 是近年来十分流行的一个数据可视化开发库. 采用BSD协议 源码:https://github.com/mbostock/d3 官网:http://d ...
- D3.js 用层画条形图
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 【 D3.js 入门系列 --- 3 】 做一个简单的图表!
前面说了几节,都是对文字进行处理,这一节中将用 D3.js 做一个简单的柱形图. 做柱形图有很多种方法,比如用 HTML 的 div 标签,或用 svg . 推荐用 SVG 来做各种图形.SVG 意为 ...
随机推荐
- Exception in thread "main" java.lang.AbstractMethodError
参考https://stackoverflow.com/questions/15758151/class-conflict-when-starting-up-java-project-classmet ...
- 吴裕雄--天生自然python机器学习:K-近邻算法介绍
k-近邻算法概述 简单地说,谷近邻算法采用测量不同特征值之间的距离方法进行分类. 优 点 :精度高.对异常值不敏感.无数据输入假定. 缺点:计算复杂度高.空间复杂度高. 适用数据范围:数值型和标称型. ...
- z-scores|zα
6.2 Areas Under the Standard Normal Curve Property 4: Almost all the area under the standard normal ...
- SpringBoot 将自制的Starter 发布到远程公服
上一篇文章:就是简单的介绍了如何自己制作一个starter ,由于上篇文章只是我个人的笔记,就是将其中重要的部分写出来了,少了其他的基础步骤,但是这个我自己就能看懂,也算不上是一篇好的博客,只能算是笔 ...
- Django学习之模型层
模型层 查看orm内部sql语句的方法的方法 1.如果是queryset对象,那么可以点query直接查看该queryset的内部sql语句 2.在Django项目的配置文件中,配置一下参数即可实现所 ...
- 牛客-小w的a=b问题
题目传送门 sol1:老实做,预处理出所有2到1e5的素数,对所有数进行分解质因数,然后对比因子个数.感觉有点卡常,用了快读然后多次优化之后才过的,map也用上了. 素数筛,快速分解质因数 #incl ...
- HttpClient系统日志配置
详细介绍在:http://hc.apache.org/httpclient-3.x/logging.html 一般使用context logging基本够用,context logging解释原文如下 ...
- 为什么jdk1.8 HashMap的容量一定要是2的n次幂
一.jdk1.8中,对“HashMap的容量一定要是2的n次幂”做了严格控制 1.默认初始容量: [Java] 纯文本查看 复制代码 ? 1 2 3 4 /** * The default init ...
- 合并cookie,提取json数据
发送的第3个请求需要前两个请求的cookie,需要对cookie进行合并 发送的请求数据来自于json数据中的某个键值. 这里是删除所有的对话主题目录,每一个目录有一个id,发起删除对话主题目录的请求 ...
- 实战:Zabbix 3.4邮件报警配置
环境:CentOS 7 + Zabbix 3.4 如果只是监测并不能满足需求,还需要通过邮件的方式对问题进行报警.接下来进行实战操作: 一.安装邮件发送工具 mailx 如果CentOS 有邮件服务, ...