D3.js 实现带伸缩时间轴拓扑图的示例代码
程序员文章站
2022-06-15 15:54:00
效果图:
基于d3-v5, 依赖dagre-d3, 直接上代码:
...
效果图:
基于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(八)
下一篇: avaScript原型链的继承过程