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 相关文章推荐
JSON 学习之JSON in JavaScript详细使用说明
Feb 23 Javascript
Jquery 数据选择插件Pickerbox使用介绍
Aug 24 Javascript
iframe里面的元素触发父窗口元素事件的jquery代码
Oct 19 Javascript
jQuery获取radio选中项的值实例
Jun 18 Javascript
Javascript中内建函数reduce的应用详解
Oct 20 Javascript
详解用node编写自己的cli工具
May 23 Javascript
vue webpack开发访问后台接口全局配置的方法
Sep 18 Javascript
Vue.js中对css的操作(修改)具体方式详解
Oct 30 Javascript
vue列表单项展开收缩功能之this.$refs的详解
May 05 Javascript
js实现贪吃蛇游戏 canvas绘制地图
Sep 09 Javascript
jQuery实现容器间的元素拖拽功能
Dec 01 jQuery
k8s node节点重新加入master集群的实现
Feb 22 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
把77A收信机改造成收音机
2021/03/02 无线电
Smarty结合Ajax实现无刷新留言本实例
2007/01/02 PHP
php中var_export与var_dump的区别分析
2010/08/21 PHP
Windows 下的 PHP-PEAR 安装方法
2010/11/20 PHP
php 判断字符串中是否包含html标签
2014/02/17 PHP
PHP PDO fetch 模式各种参数的输出结果一览
2015/01/07 PHP
PHP如何使用Memcached
2016/04/05 PHP
使用PHP反射机制来构造&quot;CREATE TABLE&quot;的sql语句
2019/03/21 PHP
基于PHP实现邮箱验证激活过程详解
2020/10/28 PHP
javascript concat数组累加 示例
2009/09/03 Javascript
图片轮换效果实现代码(点击按钮停止执行)
2013/04/12 Javascript
jQuery function的正确书写方法
2013/08/02 Javascript
防止jQuery ajax Load使用缓存的方法小结
2014/02/22 Javascript
chrome不支持form.submit的解决方案
2015/04/28 Javascript
JavaScript实现点击按钮就复制当前网址
2015/12/14 Javascript
javascript代码优化的8点总结
2018/01/29 Javascript
基于Koa(nodejs框架)对json文件进行增删改查的示例代码
2019/02/02 NodeJs
如何在Angular应用中创建包含组件方法示例
2019/03/23 Javascript
NodeJs生成sitemap站点地图的方法示例
2019/06/11 NodeJs
JS数组方法concat()用法实例分析
2020/01/18 Javascript
python如何求解两数的最大公约数
2018/09/27 Python
python异常处理之try finally不报错的原因
2020/05/18 Python
pytorch中的weight-initilzation用法
2020/06/24 Python
keras自动编码器实现系列之卷积自动编码器操作
2020/07/03 Python
酒店经理职责
2014/01/30 职场文书
《颐和园》教学反思
2014/02/26 职场文书
运动会拉拉队口号
2014/06/09 职场文书
教师党的群众路线学习心得体会
2014/11/04 职场文书
医院合作意向书范本
2015/05/08 职场文书
朝花夕拾读书笔记
2015/06/29 职场文书
2015年教师节广播稿
2015/08/19 职场文书
教你使用pyinstaller打包Python教程
2021/05/27 Python
FP-growth算法发现频繁项集——发现频繁项集
2021/06/24 Python
JS的深浅复制详细
2021/10/16 Javascript
CentOS7安装GlusterFS集群以及相关配置
2022/04/12 Servers
Python实战实现爬取天气数据并完成可视化分析详解
2022/06/16 Python