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 相关文章推荐
js实现网页随机切换背景图片的方法
Nov 01 Javascript
jquery实现勾选复选框触发事件给input赋值
Feb 01 Javascript
jQuery validate插件功能与用法详解
Dec 15 Javascript
vue项目实战总结篇
Feb 11 Javascript
JavaScript中字符串的常用操作方法及特殊字符
Mar 18 Javascript
Vue2.0学习系列之项目上线的方法步骤(图文)
Sep 25 Javascript
详解关于Vuex的action传入多个参数的问题
Feb 22 Javascript
WebGL three.js学习笔记之阴影与实现物体的动画效果
Apr 25 Javascript
JavaScript动态检测密码强度原理及实现方法详解
Jun 11 Javascript
微信小程序如何实现在线客服功能
Oct 16 Javascript
vue 使用 canvas 实现手写电子签名
Mar 06 Javascript
Vue全家桶入门基础教程
May 14 Vue.js
阿望教你用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
openflashchart 2.0 简单案例php版
2012/05/21 PHP
Zend studio文件注释模板设置方法
2013/09/29 PHP
php提交表单发送邮件的方法
2015/03/20 PHP
CentOS下与Apache连接的PHP多版本共存方案实现详解
2015/12/19 PHP
PHP 设计模式系列之 specification规格模式
2016/01/10 PHP
PHP crypt()函数的用法讲解
2019/02/15 PHP
基于jquery的网站幻灯片切换效果焦点图代码
2013/09/15 Javascript
js简单实现根据身份证号码识别性别年龄生日
2013/11/29 Javascript
JSuggest自动匹配下拉框使用方法(示例代码)
2013/12/27 Javascript
JavaScript提高加载和执行效率的方法
2017/02/03 Javascript
JScript实现地址选择功能
2017/08/15 Javascript
React Native实现地址挑选器功能
2017/10/24 Javascript
JS实现面向对象继承的5种方式分析
2018/07/21 Javascript
使用form-create动态生成vue自定义组件和嵌套表单组件
2019/01/18 Javascript
Vue Components 数字键盘的实现
2019/09/18 Javascript
详解JavaScript作用域、作用域链和闭包的用法
2020/09/03 Javascript
vue打开新窗口并实现传参的图文实例
2021/03/04 Vue.js
[50:20]DOTA2上海特级锦标赛主赛事日 - 5 总决赛Liquid VS Secret第四局
2016/03/06 DOTA
python3访问sina首页中文的处理方法
2014/02/24 Python
Python的Django框架中settings文件的部署建议
2015/05/30 Python
python 实现网上商城,转账,存取款等功能的信用卡系统
2016/07/15 Python
Python简单计算数组元素平均值的方法示例
2017/12/26 Python
Python3实现对列表按元组指定列进行排序的方法分析
2018/12/22 Python
详解python做UI界面的方法
2019/02/27 Python
使用python list 查找所有匹配元素的位置实例
2019/06/11 Python
使用python分析统计自己微信朋友的信息
2019/07/19 Python
django中的图片验证码功能
2019/09/18 Python
python next()和iter()函数原理解析
2020/02/07 Python
Pygame的程序开始示例代码
2020/05/07 Python
Python之Sklearn使用入门教程
2021/02/19 Python
CSS3圆角边框和边界图片效果实例
2016/07/01 HTML / CSS
HTML5 Video/Audio播放本地文件示例介绍
2013/11/18 HTML / CSS
美体小铺法国官方网站:The Body Shop法国
2020/06/04 全球购物
机修工岗位职责
2013/11/24 职场文书
城管执法人员个人对照检查材料思想汇报
2014/09/29 职场文书
护理心得体会范文
2016/01/22 职场文书