要实现如下动态的效果,随着点击传入的数据变化而变化
根据设计图要求,主要有以下及部分
-
控制箭头样式
-
左侧整个层的name显示
-
每个zone的显示与正常值的差别时间
-
每个zone根据时间显示大小
-
异常的zone置红
-
要显示两层结构,大小要随着div变化
-
其他.....
使用angular8开始吧
html部分
<div class="flow-charts">
<div id="report-flow-charts" class="flow-components"></div>
</div>
引入g6,初始定义一些参数
graph: any; //方便本页面使用g6实例化的实体
configData 初始的配置
import G6 from "@antv/g6";
export class FlowChartDisplayComponent implements OnInit, OnDestroy {
/** * g6生成流程图的配置参数 */
configData = { // g6默认的配置
container: "", // 指定图画布的容器 id,与第 9 行的容器对应 // 画布宽高
width: null,
height: null,
fitView: false, // node大小和连接线大小自适应
linkCenter: true,
fitViewPadding: 20,
animate: true, // Boolean,可选,全局变化时否使用动画过度
modes: { // 弹框的样式
default: [
{ type: "tooltip", // 提示框
formatText(model: any) { // 提示框文本内容
if (model.data.nodeType === "nodesItem") {
const text = `diff_time:${model.label}<br/>step_time:${model.data.step_time}S<br/>target_time:${model.data.target_time}S`; return text;
} }, shouldBegin: (e) => { // 当点击的节点/边/ combo 的 id 为 'id1' 时,不允许该 behavior 发生 if (e.item.getModel().data.nodeType == "zoneNode") return false; return true; }, }, ], edit: [], }, plugins: [], };
graph:any
}
modes用于中可以设置g6的交互模式,tooltip用来显示提示框,可在里面定义提示框内容
{ type: "tooltip", // 提示框 formatText(model: any) { // 提示框文本内容 if (model.data.nodeType === "nodesItem") { const text = `diff_time:${model.label}<br/>step_time:${model.data.step_time}S<br/>target_time:${model.data.target_time}S`; return text; } }, shouldBegin: (e) => { // 当点击的节点/边/ combo 的 id 为 'id1' 时,不允许该 behavior 发生 if (e.item.getModel().data.nodeType == "zoneNode") return false; return true; }, },
- formatText:提示框内容,return 出来
- shouldBegin:定义显示提示框的条件,return true则显示,否则不显示
开始g6相关方法的定义
第一步:constructor中发送ajax
constructor(){}
第二步:把ajax得到的数据处理成g6需要的数据格式。先定义处理中公共的部分,当然封装起来是更好的!具体的都有备注。后面将用到
assignG6Init(data: ReprtFlowchartRes){
//公用数据部分 const nodeOverFillColor = "#DF0024"; // 超出部分颜色 const nodeDefaultFillColor = "#5A7C91"; // 节点正常色 const configDOM = $("#report-flow-charts"); const configDOMWidth = configDOM.width(); const configDOMHeigth = configDOM.height(); let configTextWidth = 0; // 固定文字的宽度 const configRightIconWidth = 50; let bkZondeNum = 0; // 背景zone的数量 let nodeKeyIDNum = 0; // 连线zone的数量 let nodeItemHeight = 0; // 每个Node节点的高度 let nodeDeafultHeight = 0; //节点固定宽度大小 // 节点的总数据 let g6InitData = { nodes: null, edges: null, };//转换节点连接线部分 let edgesData = [];
}
开始转换数据了,并不复杂,封装起来引入调用,当然是更好的,其中都有一些简单注释!
data.LinkInfoList.forEach((item) => { let edgesItem = { source: item.source, target: item.target, type: "line-arrow", size: 1, color: "#5A7C91", // 该边连入 source 点的第 0 个 anchorPoint, sourceAnchor: 0, // 该边连入 target 点的第 0 个 anchorPoint, targetAnchor: 1, style: { radius: 20, offset: 2, // endArrow: true, lineWidth: 2, stroke: "#5A7C91", // offset: 2, // 拐弯处距离节点最小距离 // radius: 10, // 拐弯处的圆角弧度,若不设置则为直角 // lineWidth: 1, // stroke: '#5A7C91', color: "#5A7C91", size: 1, lineDash: [0, 0], cursor: "pointer", endArrow: { path: "M 0,0 L 8,4 L 8,-4 Z", // 箭头的偏移量,负值代表向 x 轴正方向移动 d: -20, // v3.4.1 后支持各样式属性 fill: "#5A7C91", // opacity: 0.8, }, }, }; if (item.line_type === 1) { edgesItem.style.lineDash = [1, 1]; } edgesData.push(edgesItem); }); g6InitData.edges = edgesData; // line_type: 1 // 转换nodes节点部分 let g6DataList = []; let g6BKInitList = []; data.DataList.forEach((item, index) => { // 设置背景图 let zoneNode: any; if (!g6BKInitList.includes(item.zone_id)) { g6BKInitList.push(item.zone_id); let bkData = JSON.parse(JSON.stringify(item)); bkData.nodeType = "zoneNode"; bkZondeNum++; zoneNode = { id: item.zone_id, type: "rect", x: configDOMWidth / 2, y: null, label: item.zone_name.trim(), anchorPoints: [], size: null, style: { fill: "#fff", stroke: "#000", opacity: 0.1, fontSize: 20, lineWidth: 1, }, labelCfg: { // 节点上的标签文本样式配置 position: "left", offset: 0, // refX: 300, style: { fill: "#000", // 节点标签文字颜色 writingMode: "vertical - lr", fontSize: 14, fontWeight: 700, lineWidth: 2, cursor: "pointer", }, }, data: bkData, }; } const nodeData = JSON.parse(JSON.stringify(item)); nodeData.nodeType = "nodesItem"; nodeKeyIDNum++; let nodesItem = { id: item.key_id, type: "rect", x: null, y: null, label: item.diff_time > 0 ? "+" + item.diff_time + "S" : item.diff_time + "S", size: null, anchorPoints: [ // [0, 0], // [0.5, 0], [1, 0.5], [0.5, 0], ], style: { fill: item.is_over ? nodeOverFillColor : nodeDefaultFillColor, // 节点填充色 stroke: "", lineWidth: 0, cursor: "pointer", }, labelCfg: { // 节点上的标签文本样式配置 position: "bottom", offset: 10, // refX: -20, style: { fill: "#333", // 节点标签文字颜色 }, }, data: nodeData, }; if (zoneNode) { g6DataList.push(zoneNode); } g6DataList.push(nodesItem); }); // 画背景图 let isZondeNodeIndex = -1; let configDOMMaxWidth = 0; let lastZoneIndex = 0; g6DataList.forEach((item, index) => { if (item.data.nodeType === "zoneNode") { lastZoneIndex = index; } }) g6DataList.forEach((item, index) => { if (item.data.nodeType === "zoneNode") { isZondeNodeIndex += 1; // 先画背景图 // index == 0 ? configDOMHeigth / 6 : (index + 1) * configDOMHeigth / 3 - configDOMHeigth / 6 //isZondeNodeIndex == 0 ? configDOMHeigth / (bkZondeNum * 2) : (isZondeNodeIndex + 1) * configDOMHeigth / bkZondeNum - configDOMHeigth / (bkZondeNum * 2) let labelWidth = (item.label.length + 1) * 8 ; if (labelWidth > configTextWidth) { configTextWidth = labelWidth; } item.labelCfg.offset = lastZoneIndex === index ? -configTextWidth + (configTextWidth / 18) : -configTextWidth ; item.y = ((isZondeNodeIndex + 1) * configDOMHeigth) / bkZondeNum - configDOMHeigth / (bkZondeNum * 2); item.size = [configDOMWidth, configDOMHeigth / bkZondeNum]; } }); let isNodeItemIndex = -1; let isNodeStepSum = 0; g6DataList.forEach((item) => { if (item.data.nodeType === "nodesItem") { isNodeStepSum += item.data.step_time; } }); // 画各个节点之间的位置 let beforNodeAllWidth: number = 0; // 之前node的节点宽度 g6DataList.forEach((item, index) => { if (item.data.nodeType === "nodesItem") { isNodeItemIndex += 1; // (configDOMWidth - configTextWidth) /setp_time总和 * 单个step_time/2 const nowWidth = ((configDOMWidth - configTextWidth) / isNodeStepSum) * (item.data.step_time / 2); // const nowX = nowWidth + configTextWidth; // 计算出大小 item.size = [nowWidth, configDOMHeigth / nodeKeyIDNum / 3]; // 获取高度 if (!nodeItemHeight) { nodeItemHeight = configDOMHeigth / nodeKeyIDNum / 3; } //计算x // beforNodeAllWidth === 0 ? nowWidth / 2 + configTextWidth : nowWidth + beforNodeAllWidth; item.x = beforNodeAllWidth === 0 ? nowWidth / 2 + configTextWidth : nowWidth + beforNodeAllWidth; beforNodeAllWidth = item.x + nowWidth; // item.x + nowWidth * nodeKeyIDNum / 2 let nodeFdIndex = g6DataList.findIndex((ele) => { return ele.id === item.data.zone_id; }); item.y = g6DataList[nodeFdIndex].y; } }); // 设置连接线样式 g6InitData.edges.forEach((item) => { // 设置箭头样式 item.style.endArrow.d = nodeItemHeight ? -nodeItemHeight / 2 : -20; }); g6InitData.nodes = g6DataList; this.g6InitData = g6InitData;
第三步:开始渲染g6的DOM
当然需要区分开了,第一次初始化,还是更新操作。不然很影响渲染性能。
g6Init( clientData: any, containerID: string, divWidth: number, divHeight: number, isChanged: boolean ) {
}
有数据后,开始操作把
初次渲染的方法
this.configData.container = containerID; // 指定图画布的容器 id,与第 9 行的容器对应 // 画布宽高 this.configData.width = divWidth; this.configData.height = divHeight; // 重新计算画布大小 this.graph.changeSize(divWidth, divHeight); // graph是Graph的实例 this.g6ConfigEvent(this.graph); this.graph.changeData(data);
数据变化更新DOM的方法
this.configData.container = containerID; // 指定图画布的容器 id,与第 9 行的容器对应 // 画布宽高 this.configData.width = divWidth; this.configData.height = divHeight; G6.registerEdge( "line-arrow", { getPath(points) { const startPoint = points[0]; const endPoint = points[1]; return [ ["M", startPoint.x, startPoint.y], ["L", endPoint.x, startPoint.y], ["L", endPoint.x, endPoint.y], ]; }, }, "line" ); /** * 实例化一些插件 */ // 实例化 minimap 插件 const minimap = new G6.Minimap({ size: [50, 80], className: "minimap", type: "delegate", width: 50, height: 20, }); // 实例化 grid 插件 // const grid = new G6.Grid(); // this.configData.plugins = [grid]; // 创建 G6 图实例 this.graph = new G6.Graph(this.configData); // plugins: [minimap], // 将 minimap 实例配置到图上 // 点击节点 this.g6ConfigEvent(this.graph); // 读取数据 this.graph.data(data); // 渲染图 this.graph.render(); // 动态更新组件样式
请注意,graph.render()用来更新DOM,不然会出现多个g6的DOM的情况。更新数据时,不要忘了加。
第四步:公共操作方法的封装,只写一部分,后面大家自己记得写
将g6实例传入,其他封装类似下面代码。还有其他的方法就省略了,
// g6配置的事件绑定函数等 g6ConfigEvent(graph: any) { // 点击节点 graph.on("node:click", (e) => { }); }