D3.js 实现带伸缩时间轴拓扑图的示例代码


Posted in Javascript onJanuary 20, 2020

效果图:

D3.js 实现带伸缩时间轴拓扑图的示例代码

基于d3-v5, 依赖dagre-d3, 直接上代码:

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <style>
  svg {
   border: 1px solid darkcyan;
  }

  /* 拓扑图--start */
  /* 节点状态颜色 */
  g.type-current>circle {
   fill: #FFAC27;
  }

  g.type-success>circle {
   fill: #9270CA;
  }

  g.type-fail>circle {
   fill: #67C23A;
  }

  g.type-done>circle {
   fill: #E8684A;
  }

  /* 拓扑图--end */

  /* 坐标轴-start */
  .axis path,
  .axis line {
   fill: none;
   stroke: #DCDCDC;
   shape-rendering: crispEdges;
  }

  .axis text {
   font-family: sans-serif;
   font-size: 12px;
   fill: #999999;
  }

  .axis .x2-axis text {
   font-size: 14px;
   font-weight: 400;
   fill: #333;
  }

  .axis .x2-axis .tick {
   stroke-width: 2px;
  }


  /* 坐标轴-end */
 </style>
</head>
<script src=" http://d3js.org/d3.v5.min.js "></script>
<script src="https://cdn.bootcss.com/dagre-d3/0.6.3/dagre-d3.js"></script>

<body>
</body>
<script>

 let nodeInfo = [{
  id: 0,
  label: "",
  status: 'success',
  date: 1575129600000
 }, {
  id: 1,
  label: "",
  status: 'fail',
  date: 1578376890000
 }, {
  id: 2,
  label: '',
  status: 'success',
  date: 1578376890000
 }, {
  id: 3,
  label: '',
  status: 'fail',
  date: 1578895290000
 }, {
  id: 4,
  label: '',
  status: 'current',
  date: 1578895290000
 }, {
  id: 5,
  label: '',
  status: 'done',
  date: 1579327290000
 }, {
  id: 6,
  label: '',
  status: 'done',
  date: 1579932090000
 }, {
  id: 7,
  label: '',
  status: 'done',
  date: 1581487290000
 }, {
  id: 8,
  label: '',
  status: 'success',
  date: 1583461994000
 }]
 let lineInfo = [
  { from: 0, to: 1 },
  { from: 0, to: 2 },
  { from: 0, to: 3 },
  { from: 2, to: 4 },
  { from: 2, to: 5 },
  { from: 3, to: 6 },
  { from: 6, to: 7 },
  { from: 6, to: 8 },
 ]

 let nodeMap = new Map() //节点信息map
 let nodeDomMap = new Map() //节点dom--map
 let timeArr = [] //存储时间

 const width = 1200
 const height = 400
 const padding = { top: 0, bottom: 40, left: 40, right: 40 }

 // 节点信息转化为map
 nodeInfo.forEach(item => {
  nodeMap.set(item.id, item);
  timeArr.push(item.date)
 })
 let max = new Date(d3.max(timeArr))
 let min = new Date(d3.min(timeArr))
 maxY = max.getFullYear()
 maxM = max.getMonth()
 minY = min.getFullYear()
 minM = min.getMonth()

 // 创建画布 svg
 let svg = d3.select("body").append("svg")
  .attr("id", "svg-canvas")
  .attr("preserveAspectRatio", "xMidYMid meet")
  .attr("viewBox", `0 0 ${width} ${height}`)

 // 初始化元素
 let background = svg.append("rect").attr("class", "bg")
 let view = svg.append("g").attr("class", "view")
 let grid = svg.append("g").attr("class", "grid")
 let axis = svg.append("g").attr("class", "axis")
 let separateLine = svg.append("line").attr("class", "separate-line")

 // 绘制箭头以供引用
 d3.select("#svg-canvas").append("defs").append("marker")
  .attr("id", "triangle").attr("viewBox", "0 0 10 10")
  .attr("refX", "17").attr("refY", "5")
  .attr("markerWidth", "6").attr("markerHeight", "6")
  .attr("orient", "auto").append("path")
  .attr("d", "M 0 0 L 10 5 L 0 10 z").style("fill", "#bbbbbb")

 // 添加背景板 rect
 background.attr("fill", "#FAFAFA")
  .attr("x", 0).attr("y", 0)
  .attr("width", width).attr("height", height - padding.bottom)
 const monthNum = d3.timeMonth.count(min, max) // 区间月份数量

 // 确定比例尺
 let xScale = d3.scaleTime()
  .domain([new Date(minY, minM, 1), new Date(maxY, ++maxM, 1)])
  .range([0, width - padding.left - padding.right])

 // 坐标轴文本格式化
 let formatDay = d3.axisBottom(xScale).tickFormat((d, i) => {
  const date = new Date(d)
  const day = date.getDate()
  return `${day === 1 ? "" : day}` // 如果是1号, 不显示刻度,直接由xAxis2显示年月
 })
 let formatMonth = d3.axisBottom(xScale).ticks(d3.timeMonth.every(1)).tickPadding(6).tickSizeInner(20).tickFormat((d, i) => {
  const date = new Date(d)
  const mon = date.getMonth() + 1
  const year = date.getFullYear()
  return `${year} - ${mon > 9 ? mon : "0" + mon}`
 })
 axis.attr('transform', `translate(${padding.left},${height - padding.bottom})`)
 let xAxisDay = axis.append("g")
  .attr("class", "x-axis").call(formatDay)
 let xAxisMonth = axis.append("g")
  .attr("class", "x2-axis").call(formatMonth)


 // 绘制x网格
 const lineGroup = grid.attr("transform", `translate(${padding.left},0)`)
  .selectAll("g")
  .data(xScale.ticks(monthNum))
  .enter().append("g")
 lineGroup.append("line")
  .attr("x1", d => { return xScale(new Date(d)) })
  .attr("x2", d => { return xScale(new Date(d)) })
  .attr("y1", padding.top)
  .attr("y2", height - padding.bottom)
  .attr("class", "grid-line")
  .style("stroke", "#DCDCDC")
  .style("stroke-dasharray", 6)

 // 添加坐标轴与拓扑图分隔线
 separateLine.style("stroke", "#DCDCDC")
  .style("stroke-width", 2)
  .attr("x1", 0)
  .attr("x2", width)
  .attr("y1", height - padding.bottom)
  .attr("y2", height - padding.bottom)

 // 绘制流程图 节点--箭头
 let g = new dagreD3.graphlib.Graph()
  .setGraph({})
  .setDefaultEdgeLabel(function () { return {}; });
 g.graph().rankdir = "LR"; // 控制水平显示
 g.graph().marginx = 0;
 g.graph().marginy = 50;

 nodeInfo && nodeInfo.map((item, i) => {
  g.setNode(item.id, {
   label: item.label,
   class: "type-" + item.status,
   style: "stroke-width: 2px; stroke: #fff",
   shape: "circle",
   id: item.id
  });

 })

 lineInfo && lineInfo.map((item, i) => {
  g.setEdge(item.from, item.to,
   {
    arrowheadStyle: "stroke:none; fill: none", // 箭头头部样式
    style: "stroke:none; fill: none" //线条样式
   })

 })

 let render = new dagreD3.render();
 render(view.attr("transform", `translate(${padding.left},0)`), g);

 // 重新定位节点x坐标
 const nodesArr = d3.select(".nodes").selectAll(".node")._groups[0]
 nodesArr.forEach((item) => {
  let dom = d3.select(item)._groups[0][0]
  let id = Number(dom.id)
  let date = nodeMap.get(id).date
  const x = xScale(new Date(date));
  const y = dom.transform.animVal[0].matrix.f
  d3.select(item).attr("transform", `translate(${x},${y})`)
  nodeDomMap.set(Number(item.id), item)
 })

 // 重新绘制箭头
 lineInfo && lineInfo.map((item, i) => {
  let fromDom = nodeDomMap.get(Number(item.from))
  let toDom = nodeDomMap.get(Number(item.to))
  const [x1, y1, x2, y2] = [
   fromDom.transform.animVal[0].matrix.e,
   fromDom.transform.animVal[0].matrix.f,
   toDom.transform.animVal[0].matrix.e,
   toDom.transform.animVal[0].matrix.f,
  ]
  d3.select(".edgePaths").append("g")
   .append("line")
   .attr("class", `to-${item.to}`) // 设置唯一的class方便修改路径
   .attr("stroke-width", "2")
   .attr("stroke", "#bbbbbb")
   .style("stroke-dasharray", 8)
   .attr("marker-end", "url(#triangle)")
   .attr("x1", x1).attr("y1", y1)
   .attr("x2", x2).attr("y2", y2)

 })

 // 设置zoom参数
 let zoom = d3.zoom()
  .scaleExtent([1, 10])
  .translateExtent([[0, 0], [width, height]]) //移动的范围
  .extent([[0, 0], [width, height]])//视窗 (左上方,右下方)

 svg.call(zoom.on("zoom", reRender.bind(this)));


 // 每次缩放重定位渲染拓扑图
 function reRender() {
  const t = d3.event.transform.rescaleX(xScale) //获得缩放后的比例尺
  xAxisDay.call(formatDay.scale(t))  //重新设置x坐标轴的scale
  xAxisMonth.call(formatMonth.scale(t))  //重新设置x坐标轴的scale

  const view = d3.select(".output")
  const axis = d3.select(".axis-month")
  const grid = d3.selectAll(".grid-line")

  // 重新绘制节点
  nodesArr.forEach((item) => {
   let dom = d3.select(item)._groups[0][0]
   let id = Number(dom.id)
   let date = nodeMap.get(id).date
   const x = t(new Date(date));
   const y = dom.transform.animVal[0].matrix.f
   d3.select(item).attr("transform", `translate(${x},${y})`)
   nodeDomMap.set(Number(item.id), item)
  })

  // 重新绘制箭头
  lineInfo && lineInfo.map((item, i) => {
   let fromDom = nodeDomMap.get(Number(item.from))
   let toDom = nodeDomMap.get(Number(item.to))
   const [x1, y1, x2, y2] = [
    fromDom.transform.animVal[0].matrix.e,
    fromDom.transform.animVal[0].matrix.f,
    toDom.transform.animVal[0].matrix.e,
    toDom.transform.animVal[0].matrix.f,
   ]
   d3.select(`.to-${item.to}`)
    .attr("x1", x1).attr("y1", y1)
    .attr("x2", x2).attr("y2", y2)

  })

  //重新绘制x网格
  svg.selectAll(".grid-line")
   .attr("x1", d => { return t(new Date(d)) })
   .attr("x2", d => { return t(new Date(d)) })
 }



</script>

</html>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery打造PHP的AJAX表单提交实例
Nov 03 Javascript
jQuery 获取URL参数的插件
Mar 04 Javascript
jquery键盘事件介绍
Jan 31 Javascript
JavaScript模拟可展开、拖动与关闭的聊天窗口实例
May 12 Javascript
Java中Timer的用法详解
Oct 21 Javascript
jQuery获取及设置表单input各种类型值的方法小结
May 24 Javascript
ajax实现加载页面、删除、查看详细信息 bootstrap美化页面!
Mar 14 Javascript
详解vue在项目中使用百度地图
Mar 26 Javascript
Vue开发Html5微信公众号的步骤
Apr 11 Javascript
小程序组件之自定义顶部导航实例
Jun 12 Javascript
jQuery实现提交表单时不提交隐藏div中input的方法
Oct 08 jQuery
webpack安装配置与常见使用过程详解(结合vue)
Jun 01 Javascript
阿望教你用vue写扫雷小游戏
Jan 20 #Javascript
JavaScript Window窗口对象属性和使用方法
Jan 19 #Javascript
浅谈webpack和webpack-cli模块源码分析
Jan 19 #Javascript
js原生map实现的方法总结
Jan 19 #Javascript
Node.js操作MongoDB数据库实例分析
Jan 19 #Javascript
JS运算符简单用法示例
Jan 19 #Javascript
Vue 微信端扫描二维码苹果端却只能保存图片问题(解决方法)
Jan 19 #Javascript
You might like
php强制文件下载而非在浏览器打开的自定义函数分享
2014/05/08 PHP
PHP静态成员变量和非静态成员变量详解
2017/02/14 PHP
使用WAMP搭建PHP本地开发环境
2017/05/10 PHP
thinkphp5引入公共部分header、footer的方法详解
2018/09/14 PHP
laravel开发环境homestead搭建过程详解
2020/07/03 PHP
比较不错的JS/JQuery显示或隐藏文本的方法
2014/02/13 Javascript
JS兼容浏览器的导出Excel(CSV)文件的方法
2014/05/03 Javascript
JavaScript常用验证函数实例汇总
2014/11/25 Javascript
js运动应用实例解析
2015/12/28 Javascript
AngularJs基于角色的前端访问控制的实现
2016/11/07 Javascript
Bootstrap入门教程一Hello Bootstrap初识
2017/03/02 Javascript
jQuery遮罩层实例讲解
2017/05/11 jQuery
利用vscode编写vue的简单配置详解
2017/06/17 Javascript
浅谈angular2路由预加载策略
2017/10/04 Javascript
Vue render渲染时间戳转时间,时间转时间戳及渲染进度条效果
2018/07/27 Javascript
Vue中img的src是动态渲染时不显示的解决
2019/11/14 Javascript
js实现滑动滑块验证登录
2020/07/24 Javascript
[17:36]VG战队纪录片
2014/08/21 DOTA
从零学python系列之教你如何根据图片生成字符画
2014/05/23 Python
Python中给List添加元素的4种方法分享
2014/11/28 Python
对PyQt5的输入对话框使用(QInputDialog)详解
2019/06/25 Python
Python交互式图形编程的实现
2019/07/25 Python
纯DOM+CSS3实现简单的小风车动画
2016/09/27 HTML / CSS
Missguided美国官网:英国时尚品牌
2018/01/18 全球购物
游戏商店:Eneba
2020/04/25 全球购物
Ruby如何实现动态方法调用
2012/11/18 面试题
国际会议邀请函范文
2014/01/16 职场文书
大学毕业感言50字
2014/02/07 职场文书
创建市级文明单位实施方案
2014/03/01 职场文书
公司委托书格式范文
2014/04/04 职场文书
财务管理专业求职信
2014/06/11 职场文书
行政主管岗位职责
2015/02/03 职场文书
我是特种兵观后感
2015/06/11 职场文书
2016形势与政策学习心得体会
2016/01/12 职场文书
读《庄子》有感:美而不自知
2019/11/06 职场文书
mysql sum(if())和count(if())的用法说明
2022/01/18 MySQL