当前位置 主页 > 服务器问题 > Linux/apache问题 >

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

    栏目:Linux/apache问题 时间:2020-01-28 21:33

    效果图:


    基于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>