G6 registerNode afterDraw createNodeBox contextMenu ToolBar 的综合使用
效果:
代码:
1 import G6 from "@antv/g6"; 2 3 // 提示 4 const graphDiv = document.getElementById("container"); 5 const descriptionDiv = document.createElement("p"); 6 descriptionDiv.style.padding = 0; 7 descriptionDiv.style.margin = 0; 8 descriptionDiv.style.fontSize = "12px"; 9 descriptionDiv.style.color = "#999"; 10 descriptionDiv.innerHTML = 11 " *提示:灰色任务代表“禁用”状态,右键进行对任务的删除、启用/禁用操作。"; 12 graphDiv.appendChild(descriptionDiv); 13 // 右键 14 const contextMenu = new G6.Menu({ 15 getContent(evt) { 16 const { dataType } = evt.item.getModel(); 17 const flag = dataType === "disabled"; 18 return `<div eventType = '1' style="cursor:pointer">删除</div>${ 19 flag 20 ? '<div eventType="2" style="cursor:pointer">启用</div>' 21 : '<div eventType="3" style="cursor:pointer">禁用</div>' 22 }`; 23 }, 24 shouldBegin(evt) { 25 const { dataType } = evt.item.getModel(); 26 if (dataType && dataType === "root") { 27 return false; 28 } else { 29 return true; 30 } 31 }, 32 handleMenuClick: (target, item) => { 33 console.log(item.getModel()); 34 const eventType = target.getAttribute("eventType"); 35 switch (eventType) { 36 default: 37 return; 38 case "1": 39 console.log(1, eventType); 40 break; 41 case "2": 42 console.log(2, eventType); 43 break; 44 case "3": 45 console.log(3, eventType); 46 break; 47 } 48 }, 49 offsetX: 16, 50 offsetY: 0, 51 itemTypes: ["node"] 52 }); 53 // 工具栏按钮定制 54 const toolbar = new G6.ToolBar({ 55 position: { x: 700, y: 10 }, 56 getContent: () => { 57 return ` 58 <ul> 59 <li code='switch'>切换</li> 60 <li code='fitView'>适应</li> 61 </ul> 62 `; 63 }, 64 handleClick(code, graph) { 65 switch (code) { 66 case "switch": // 横、纵切换 67 const controler = graph.get("layoutController"); 68 const { rankdir } = controler.layoutMethod; 69 if (rankdir === "H") { 70 controler.updateLayoutCfg({ rankdir: "LR" }); 71 } else { 72 controler.updateLayoutCfg({ rankdir: "H" }); 73 } 74 controler.relayout(); 75 graph.fitView(); 76 break; 77 case "fitView": // 适应屏幕 78 graph.fitView(); 79 break; 80 default: 81 return; 82 } 83 } 84 }); 85 86 const ERROR_COLOR = "#F5222D"; 87 const getNodeConfig = (node) => { 88 if (node.nodeError) { 89 return { 90 basicColor: ERROR_COLOR, 91 fontColor: "#FFF", 92 borderColor: ERROR_COLOR, 93 bgColor: "#E66A6C" 94 }; 95 } 96 let config = { 97 basicColor: "#5B8FF9", 98 fontColor: "#5B8FF9", 99 borderColor: "#5B8FF9", 100 bgColor: "#C6E5FF" 101 }; 102 return config; 103 }; 104 const nodeBasicMethod = { 105 createNodeBox: (group, config, w, h, cfg) => { 106 const nodeError = cfg.nodeError; 107 /* 最外面的大矩形 */ 108 const container = group.addShape("rect", { 109 attrs: { 110 x: 3, 111 y: 0, 112 width: w, 113 height: h, 114 fill: config.bgColor, 115 stroke: config.borderColor, 116 radius: 2, 117 cursor: "pointer" 118 }, 119 name: "rect-shape" 120 }); 121 /* 左边的粗线 */ 122 group.addShape("rect", { 123 attrs: { 124 x: 3, 125 y: 0, 126 width: 3, 127 height: h, 128 fill: config.basicColor, 129 radius: 1.5 130 }, 131 name: "left-border-shape" 132 }); 133 // 启用、禁用 按钮 外框 134 const ipRect = group.addShape("rect", { 135 attrs: { 136 fill: nodeError ? "transparent" : "#FFF", 137 stroke: nodeError ? "rgba(255,255,255,0.65)" : null, 138 radius: 2, 139 cursor: "pointer", 140 opacity: 0 141 }, 142 name: "ip-container-shape" 143 }); 144 // 启用、禁用 按钮 文字 145 const ipText = group.addShape("text", { 146 attrs: { 147 text: cfg.ip, 148 x: 40, 149 y: 19, 150 fontSize: 12, 151 textAlign: "left", 152 textBaseline: "middle", 153 fill: nodeError ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.65)", 154 cursor: "pointer", 155 opacity: 0 156 }, 157 name: "ip-text-shape" 158 }); 159 // 移除 按钮外框 160 const moveItemRect = group.addShape("rect", { 161 attrs: { 162 fill: nodeError ? "transparent" : "#E66A6C", 163 stroke: nodeError ? "rgba(255,255,255,0.65)" : null, 164 radius: 2, 165 cursor: "pointer", 166 opacity: 0 167 }, 168 name: "remove-rect" 169 }); 170 // 移除 按钮文字 171 const moveItemText = group.addShape("text", { 172 attrs: { 173 text: "移除", 174 x: 40, 175 y: 19, 176 fontSize: 12, 177 textAlign: "left", 178 textBaseline: "middle", 179 fill: nodeError ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.65)", 180 cursor: "pointer", 181 opacity: 0 182 }, 183 name: "remove-text" 184 }); 185 const ipBBox = ipText.getBBox(); 186 const ipTextX = w - 20 - 2 * ipBBox.width; 187 const ipTextY = ipBBox.minY - 5; 188 const moveItemTextX = w - 10 - ipBBox.width; 189 const moveItemTextY = ipBBox.minY - 5; 190 ipText.attr({ 191 x: ipTextX 192 }); 193 ipRect.attr({ 194 x: ipTextX - 4, 195 y: ipTextY, 196 width: ipBBox.width + 8, 197 height: ipBBox.height + 10 198 }); 199 moveItemText.attr({ 200 x: moveItemTextX + 4 201 }); 202 moveItemRect.attr({ 203 x: moveItemTextX, 204 y: moveItemTextY, 205 width: ipBBox.width + 8, 206 height: ipBBox.height + 10 207 }); 208 // 任务 名称文字 209 group.addShape("text", { 210 attrs: { 211 text: cfg.name, 212 x: 12, 213 y: 19, 214 fontSize: 14, 215 fontWeight: 700, 216 textAlign: "left", 217 textBaseline: "middle", 218 fill: config.fontColor, 219 cursor: "pointer" 220 }, 221 name: "name-text-shape" 222 }); 223 // 任务 内容文字 224 group.addShape("text", { 225 attrs: { 226 text: cfg.keyInfo, 227 x: 12, 228 y: 45, 229 fontSize: 14, 230 textAlign: "left", 231 textBaseline: "middle", 232 fill: config.fontColor, 233 cursor: "pointer" 234 }, 235 name: "bottom-text-shape" 236 }); 237 return container; 238 }, 239 afterDraw: (cfg, group) => { 240 // 添加 移入/移出 事件 241 const ipLine = group.find( 242 (element) => element.get("name") === "ip-container-shape" 243 ); 244 const ipBG = group.find( 245 (element) => element.get("name") === "ip-text-shape" 246 ); 247 const removeRect = group.find( 248 (element) => element.get("name") === "remove-rect" 249 ); 250 const removeText = group.find( 251 (element) => element.get("name") === "remove-text" 252 ); 253 const onMouseEnter = () => { 254 ipLine && ipLine.attr("opacity", 1); 255 ipBG && ipBG.attr("opacity", 1); 256 removeRect && removeRect.attr("opacity", 1); 257 removeText && removeText.attr("opacity", 1); 258 graph.get("canvas").draw(); 259 }; 260 const onMouseLeave = () => { 261 ipLine && ipLine.attr("opacity", 0); 262 ipBG && ipBG.attr("opacity", 0); 263 removeRect && removeRect.attr("opacity", 0); 264 removeText && removeText.attr("opacity", 0); 265 graph.get("canvas").draw(); 266 }; 267 const lineclick = (e) => { 268 console.log(e.target.getParent().get("item").getModel()); 269 }; 270 group.on("mouseenter", () => { 271 onMouseEnter(); 272 }); 273 group.on("mouseleave", () => { 274 onMouseLeave(); 275 }); 276 ipBG && 277 ipBG.on("click", (e) => { 278 lineclick(e); 279 }); 280 ipLine && 281 ipLine.on("click", (e) => { 282 lineclick(e); 283 }); 284 } 285 }; 286 G6.registerNode( 287 "card-node", 288 { 289 draw: (cfg, group) => { 290 const config = getNodeConfig(cfg); 291 /* 最外面的大矩形 */ 292 const container = nodeBasicMethod.createNodeBox( 293 group, 294 config, 295 140, 296 60, 297 cfg 298 ); 299 return container; 300 }, 301 afterDraw: nodeBasicMethod.afterDraw, 302 getAnchorPoints() { 303 return [ 304 [0.5, 0], 305 [0.5, 1], 306 [0, 0.5], 307 [1, 0.5] 308 ]; 309 } 310 }, 311 "rect" 312 ); 313 G6.registerNode("sql", { 314 drawShape(cfg, group) { 315 const rect = group.addShape("rect", { 316 attrs: { 317 width: 100, 318 height: 50, 319 radius: 5, 320 stroke: "#5B8FF9", 321 fill: "#C6E5FF" 322 }, 323 name: "rect-shape" 324 }); 325 group.addShape("text", { 326 attrs: { 327 text: cfg.name, 328 x: 50, 329 y: 25, 330 fill: "#00287E", 331 fontSize: 14, 332 textAlign: "center", 333 textBaseline: "middle", 334 fontWeight: "bold" 335 }, 336 name: "text-shape" 337 }); 338 return rect; 339 }, 340 // 设置 节点 之间 锚点 连接位置 341 getAnchorPoints() { 342 return [ 343 [0.5, 0], // top-center 344 [0.5, 1] // bottom-center 345 ]; 346 } 347 }); 348 const data = { 349 nodes: [ 350 { 351 id: "1", 352 dataType: "root", 353 name: "等级1", 354 type: "sql" 355 }, 356 { 357 name: "任务1", 358 ip: "启用", 359 nodeError: true, 360 keyInfo: 361 "lsy3.novalocalsy3.novalocalsy3.novalocalsy3.novalocalsy3.novaloca", 362 id: "2", 363 comboId: "A", 364 dataType: "disabled", 365 type: "card-node" 366 }, 367 { 368 name: "任务1", 369 ip: "启用", 370 nodeError: true, 371 keyInfo: "lsy3.novaloca", 372 id: "12", 373 comboId: "A", 374 dataType: "disabled", 375 type: "card-node" 376 }, 377 { 378 name: "任务1", 379 ip: "启用", 380 nodeError: true, 381 keyInfo: "lsy3.novaloca", 382 id: "22", 383 comboId: "A", 384 dataType: "disabled", 385 type: "card-node" 386 }, 387 { 388 name: "任务1", 389 ip: "启用", 390 nodeError: true, 391 keyInfo: "lsy3.novaloca", 392 id: "32", 393 comboId: "A", 394 dataType: "disabled", 395 type: "card-node" 396 }, 397 { 398 id: "3", 399 name: "任务2", 400 comboId: "A", 401 ip: "禁用", 402 nodeError: false, 403 keyInfo: "lsy3.novaloca", 404 type: "card-node" 405 }, 406 { 407 id: "13", 408 name: "任务2", 409 comboId: "A", 410 ip: "禁用", 411 nodeError: false, 412 keyInfo: "lsy3.novaloca", 413 type: "card-node" 414 }, 415 { 416 id: "4", 417 dataType: "root", 418 name: "等级2", 419 type: "sql" 420 }, 421 { 422 id: "5", 423 name: "任务1", 424 comboId: "B", 425 ip: "禁用", 426 nodeError: false, 427 keyInfo: "lsy3.novaloca", 428 type: "card-node" 429 }, 430 { 431 id: "6", 432 name: "任务2", 433 comboId: "B", 434 ip: "禁用", 435 nodeError: false, 436 keyInfo: "lsy3.novaloca", 437 type: "card-node" 438 }, 439 { 440 id: "7", 441 dataType: "root", 442 name: "等级3", 443 type: "sql" 444 }, 445 { 446 id: "8", 447 name: "任务1", 448 comboId: "C", 449 ip: "禁用", 450 nodeError: false, 451 keyInfo: "lsy3.novaloca", 452 type: "card-node" 453 }, 454 { 455 id: "9", 456 name: "任务2", 457 comboId: "C", 458 ip: "禁用", 459 nodeError: false, 460 keyInfo: "lsy3.novaloca", 461 type: "card-node" 462 } 463 ], 464 edges: [ 465 { 466 source: "1", 467 target: "2" 468 }, 469 { 470 source: "1", 471 target: "12" 472 }, 473 { 474 source: "1", 475 target: "22" 476 }, 477 { 478 source: "1", 479 target: "32" 480 }, 481 { 482 source: "1", 483 target: "3" 484 }, 485 { 486 source: "1", 487 target: "13" 488 }, 489 { 490 source: "2", 491 target: "4" 492 }, 493 { 494 source: "12", 495 target: "4" 496 }, 497 { 498 source: "22", 499 target: "4" 500 }, 501 { 502 source: "32", 503 target: "4" 504 }, 505 { 506 source: "3", 507 target: "4" 508 }, 509 { 510 source: "13", 511 target: "4" 512 }, 513 { 514 source: "4", 515 target: "5" 516 }, 517 { 518 source: "4", 519 target: "6" 520 }, 521 522 { 523 source: "5", 524 target: "7" 525 }, 526 { 527 source: "6", 528 target: "7" 529 }, 530 { 531 source: "7", 532 target: "8" 533 }, 534 { 535 source: "7", 536 target: "9" 537 } 538 ], 539 combos: [ 540 { 541 id: "A" 542 }, 543 { 544 id: "B" 545 }, 546 { 547 id: "C" 548 } 549 ] 550 }; 551 552 const width = 800; 553 const height = 800; 554 const graph = new G6.Graph({ 555 container: "container", 556 width, 557 height, 558 layout: { 559 type: "dagre", 560 controlPoints: true, 561 rankdir: "H" // H 从上到下,LR 从左到右 562 }, 563 plugins: [contextMenu, toolbar], 564 defaultEdge: { 565 style: { 566 endArrow: true, 567 offset: 45, 568 stroke: "#C2C8D5" 569 } 570 }, 571 modes: { 572 default: ["drag-canvas", "zoom-canvas"] 573 }, 574 fitView: true, 575 defaultCombo: { 576 type: "rect" 577 } 578 }); 579 graph.data(data); 580 graph.render();
地址:https://codesandbox.io/s/suspicious-bose-qonry?file=/index.js
学习、交流、记录、编辑一部自己的知识库