效果图:
基于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,status: 'fail',date: 1578376890000 },{ id: 2,label: '',{ id: 3,date: 1578895290000 },{ id: 4,status: 'current',{ id: 5,status: 'done',date: 1579327290000 },{ id: 6,date: 1579932090000 },{ id: 7,date: 1581487290000 },{ id: 8,date: 1583461994000 }] let lineInfo = [ { from: 0,to: 1 },{ from: 0,to: 2 },to: 3 },{ from: 2,to: 4 },to: 5 },{ from: 3,to: 6 },{ from: 6,to: 7 },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",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",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,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",${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,y2] = [ fromDom.transform.animVal[0].matrix.e,] d3.select(`.to-${item.to}`) .attr("x1",y1) .attr("x2",y2) }) //重新绘制x网格 svg.selectAll(".grid-line") .attr("x1",d => { return t(new Date(d)) }) .attr("x2",d => { return t(new Date(d)) }) } </script> </html>原文链接:https://www.f2er.com/js/534664.html