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 相关文章推荐
extjs 3.31 TreeGrid实现静态页面加载json到TreeGrid里面
Apr 02 Javascript
html的DOM中Event对象onabort事件用法实例
Jan 21 Javascript
javascript如何操作HTML下拉列表标签
Aug 20 Javascript
JavaScript中instanceof运算符的使用示例
Jun 08 Javascript
Bootstrap 手风琴菜单的实现代码
Jan 20 Javascript
Swiper实现轮播图效果
Jul 03 Javascript
vue实现文章内容过长点击阅读全文功能的实例
Dec 28 Javascript
JS实现基于拖拽改变物体大小的方法
Jan 23 Javascript
vue实现通讯录功能
Jul 14 Javascript
JS动画实现回调地狱promise的实例代码详解
Nov 08 Javascript
一文了解Vue中的nextTick
May 06 Javascript
js点击事件的执行过程实例分析【冒泡与捕获】
Apr 11 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继承的一个应用
2011/09/06 PHP
PHP中使用gettext解决国际化问题的例子(i18n)
2014/06/13 PHP
PHP内核探索之解释器的执行过程
2015/12/22 PHP
YII中Ueditor富文本编辑器文件和图片上传的配置图文教程
2017/03/15 PHP
php+ajax实现商品对比功能示例
2019/04/13 PHP
laravel框架与其他框架的详细对比
2019/10/23 PHP
jquery图片放大功能简单实现
2013/08/01 Javascript
Jquery操作radio的简单实例
2014/01/06 Javascript
详解jQuery UI库中文本输入自动补全功能的用法
2016/04/23 Javascript
AngularJS基础 ng-list 指令详解及示例代码
2016/08/02 Javascript
浅谈AngularJs指令之scope属性详解
2016/10/24 Javascript
angular.js指令中的controller、compile与link函数的不同之处
2017/05/10 Javascript
vue实现弹框遮罩点击其他区域弹框关闭及v-if与v-show的区别介绍
2018/09/29 Javascript
微信小程序云开发如何使用npm安装依赖
2019/05/18 Javascript
vue.js this.$router.push获取不到params参数问题
2020/03/03 Javascript
JS typeof fn === 'function' &amp;&amp; fn()详解
2020/08/22 Javascript
vue用ant design中table表格,点击某行时触发的事件操作
2020/10/28 Javascript
python ---lambda匿名函数介绍
2019/03/13 Python
用Python批量把文件复制到另一个文件夹的实现方法
2019/08/16 Python
使用Python画出小人发射爱心的代码
2019/11/23 Python
tensorflow的计算图总结
2020/01/12 Python
Django 博客实现简单的全文搜索的示例代码
2020/02/17 Python
python实现人机五子棋
2020/03/25 Python
Python文件操作模拟用户登陆代码实例
2020/06/09 Python
安装并免费使用Pycharm专业版(学生/教师)
2020/09/24 Python
python编写扎金花小程序的实例代码
2021/02/23 Python
浅谈HTML5新增及移除的元素
2016/06/27 HTML / CSS
size?瑞典:英国伦敦的球鞋精品店
2018/03/01 全球购物
Brydge英国:适用于Apple iPad和Microsoft Surface Pro的蓝牙键盘
2019/05/16 全球购物
意大利一家专营包包和配饰的网上商店:Borse Last Minute
2019/08/26 全球购物
初中学校军训方案
2014/05/09 职场文书
生日答谢词
2015/01/05 职场文书
体育个人工作总结
2015/02/09 职场文书
Python进度条的使用
2021/05/17 Python
React实现动效弹窗组件
2021/06/21 Javascript
python神经网络ResNet50模型
2022/05/06 Python