cloudatlas.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <div id="mountNode"></div>
  7. </head>
  8. <body>
  9. <script src="../build/g6.js"></script>
  10. <script src="../build/minimap.js"></script>
  11. <script src="./assets/jquery-3.2.1.min.js"></script>
  12. <script>
  13. const ERROR_COLOR = "#F5222D";
  14. const SIMPLE_TREE_NODE = "simple-tree-node";
  15. const TREE_NODE = "tree-node";
  16. const SOFAROUTER_TEXT_CLASS = "sofarouter-text-class";
  17. const SOFAROUTER_RECT_CLASS = "sofarouter-rect-class";
  18. const CANVAS_WIDTH = 1000;
  19. const CANVAS_HEIGHT = 600;
  20. const LIMIT_OVERFLOW_WIDTH = CANVAS_WIDTH - 100;
  21. const LIMIT_OVERFLOW_HEIGHT = CANVAS_HEIGHT - 100;
  22. const TIP_HEIGHT = 28;
  23. const getNodeConfig = node => {
  24. if (node.nodeError) {
  25. return {
  26. basicColor: ERROR_COLOR,
  27. fontColor: "#FFF",
  28. borderColor: ERROR_COLOR,
  29. bgColor: "#E66A6C"
  30. };
  31. }
  32. let config = {
  33. basicColor: "#722ED1",
  34. fontColor: "#722ED1",
  35. borderColor: "#722ED1",
  36. bgColor: "#F6EDFC"
  37. };
  38. switch (node.type) {
  39. case "root": {
  40. config = {
  41. basicColor: "#E3E6E8",
  42. fontColor: "rgba(0,0,0,0.85)",
  43. borderColor: "#E3E6E8",
  44. bgColor: "#F7F9FA"
  45. };
  46. break;
  47. }
  48. case "httpclient":
  49. case "rest":
  50. case "mvc":
  51. case "rpc":
  52. case "rpc2jvm":
  53. config = {
  54. basicColor: "#2F54EB",
  55. fontColor: "#2F54EB",
  56. borderColor: "#2F54EB",
  57. bgColor: "#F3F6FD"
  58. };
  59. break;
  60. case "db":
  61. config = {
  62. basicColor: "#52C41A",
  63. fontColor: "#52C41A",
  64. borderColor: "#52C41A",
  65. bgColor: "#F4FCEB"
  66. };
  67. break;
  68. case "msgPub":
  69. case "msgSub":
  70. case "zqmsgSend":
  71. case "zqmsgRecv":
  72. case "antqPub":
  73. case "antqSub":
  74. config = {
  75. basicColor: "#FA8C16",
  76. fontColor: "#FA8C16",
  77. borderColor: "#FA8C16",
  78. bgColor: "#FCF4E3"
  79. };
  80. break;
  81. case "zdalTair":
  82. case "zdalOcs":
  83. case "zdalOss":
  84. default:
  85. break;
  86. }
  87. return config;
  88. };
  89. const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
  90. return [
  91. ["M", x - r, y],
  92. ["a", r, r, 0, 1, 0, r * 2, 0],
  93. ["a", r, r, 0, 1, 0, -r * 2, 0],
  94. ["M", x - r + 4, y],
  95. ["L", x - r + 2 * r - 4, y]
  96. ];
  97. };
  98. const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
  99. return [
  100. ["M", x - r, y],
  101. ["a", r, r, 0, 1, 0, r * 2, 0],
  102. ["a", r, r, 0, 1, 0, -r * 2, 0],
  103. ["M", x - r + 4, y],
  104. ["L", x - r + 2 * r - 4, y],
  105. ["M", x - r + r, y - r + 4],
  106. ["L", x, y + r - 4]
  107. ];
  108. };
  109. /* 精简节点和复杂节点共用的一些方法 */
  110. const nodeBasicMethod = {
  111. createNodeBox: (group, config, width, height, isRoot) => {
  112. /* 最外面的大矩形 */
  113. const container = group.addShape("rect", {
  114. attrs: {
  115. x: 0,
  116. y: 0,
  117. width,
  118. height
  119. // fill: '#FFF',
  120. // stroke: '#000',
  121. }
  122. });
  123. if (!isRoot) {
  124. /* 左边的小圆点 */
  125. group.addShape("circle", {
  126. attrs: {
  127. x: 3,
  128. y: height / 2,
  129. r: 6,
  130. fill: config.basicColor
  131. }
  132. });
  133. }
  134. /* 矩形 */
  135. group.addShape("rect", {
  136. attrs: {
  137. x: 3,
  138. y: 0,
  139. width: width - 19,
  140. height,
  141. fill: config.bgColor,
  142. stroke: config.borderColor,
  143. radius: 2,
  144. cursor: "pointer"
  145. }
  146. });
  147. /* 左边的粗线 */
  148. group.addShape("rect", {
  149. attrs: {
  150. x: 3,
  151. y: 0,
  152. width: 3,
  153. height,
  154. fill: config.basicColor,
  155. radius: 1.5
  156. }
  157. });
  158. return container;
  159. },
  160. /* 生成树上的 marker */
  161. createNodeMarker: (group, collapsed, x, y) => {
  162. group.addShape("circle", {
  163. attrs: {
  164. x,
  165. y,
  166. r: 13,
  167. fill: "rgba(47, 84, 235, 0.05)",
  168. opacity: 0,
  169. zIndex: -2
  170. },
  171. className: "collapse-icon-bg"
  172. });
  173. group.addShape("marker", {
  174. attrs: {
  175. x,
  176. y,
  177. radius: 7,
  178. symbol: collapsed ? EXPAND_ICON : COLLAPSE_ICON,
  179. stroke: "rgba(0,0,0,0.25)",
  180. fill: "rgba(0,0,0,0)",
  181. lineWidth: 1,
  182. cursor: "pointer"
  183. },
  184. className: "collapse-icon"
  185. });
  186. },
  187. afterDraw: (cfg, group) => {
  188. /* 操作 marker 的背景色显示隐藏 */
  189. const icon = group.findByClassName("collapse-icon");
  190. if (icon) {
  191. const bg = group.findByClassName("collapse-icon-bg");
  192. icon.on("mouseenter", () => {
  193. bg.attr("opacity", 1);
  194. graph.get("canvas").draw();
  195. });
  196. icon.on("mouseleave", () => {
  197. bg.attr("opacity", 0);
  198. graph.get("canvas").draw();
  199. });
  200. }
  201. /* ip 显示 */
  202. const ipBox = group.findByClassName("ip-box");
  203. if (ipBox) {
  204. /* ip 复制的几个元素 */
  205. const ipLine = group.findByClassName("ip-cp-line");
  206. const ipBG = group.findByClassName("ip-cp-bg");
  207. const ipIcon = group.findByClassName("ip-cp-icon");
  208. const ipCPBox = group.findByClassName("ip-cp-box");
  209. const onMouseEnter = () => {
  210. this.ipHideTimer && clearTimeout(this.ipHideTimer);
  211. ipLine.attr("opacity", 1);
  212. ipBG.attr("opacity", 1);
  213. ipIcon.attr("opacity", 1);
  214. graph.get("canvas").draw();
  215. };
  216. const onMouseLeave = () => {
  217. this.ipHideTimer = setTimeout(() => {
  218. ipLine.attr("opacity", 0);
  219. ipBG.attr("opacity", 0);
  220. ipIcon.attr("opacity", 0);
  221. graph.get("canvas").draw();
  222. }, 100);
  223. };
  224. ipBox.on("mouseenter", () => {
  225. onMouseEnter();
  226. });
  227. ipBox.on("mouseleave", () => {
  228. onMouseLeave();
  229. });
  230. ipCPBox.on("mouseenter", () => {
  231. onMouseEnter();
  232. });
  233. ipCPBox.on("mouseleave", () => {
  234. onMouseLeave();
  235. });
  236. ipCPBox.on("click", () => {});
  237. }
  238. },
  239. setState: (name, value, item) => {
  240. const hasOpacityClass = [
  241. "ip-cp-line",
  242. "ip-cp-bg",
  243. "ip-cp-icon",
  244. "ip-cp-box",
  245. "ip-box",
  246. "collapse-icon-bg"
  247. ];
  248. const group = item.getContainer();
  249. const childrens = group.get("children");
  250. graph.setAutoPaint(false);
  251. if (name === "emptiness") {
  252. if (value) {
  253. childrens.forEach(shape => {
  254. if (hasOpacityClass.indexOf(shape.get("className")) > -1) {
  255. return;
  256. }
  257. shape.attr("opacity", 0.4);
  258. });
  259. } else {
  260. childrens.forEach(shape => {
  261. if (hasOpacityClass.indexOf(shape.get("className")) > -1) {
  262. return;
  263. }
  264. shape.attr("opacity", 1);
  265. });
  266. }
  267. }
  268. graph.setAutoPaint(true);
  269. }
  270. };
  271. /* 精简节点 */
  272. G6.registerNode(
  273. SIMPLE_TREE_NODE,
  274. {
  275. drawShape: (cfg, group) => {
  276. const config = getNodeConfig(cfg);
  277. const isRoot = cfg.type === "root";
  278. const nodeError = cfg.nodeError;
  279. const container = nodeBasicMethod.createNodeBox(
  280. group,
  281. config,
  282. 171,
  283. 38,
  284. isRoot
  285. );
  286. /* name */
  287. const nameText = group.addShape("text", {
  288. attrs: {
  289. text: cfg.name,
  290. x: 19,
  291. y: 19,
  292. fontSize: 14,
  293. fontWeight: 700,
  294. textAlign: "left",
  295. textBaseline: "middle",
  296. fill: config.fontColor,
  297. cursor: "pointer"
  298. }
  299. });
  300. /* 修复 nameText 超长 */
  301. fittingString(nameText, cfg.name, 133);
  302. if (nodeError) {
  303. group.addShape("image", {
  304. attrs: {
  305. x: 119,
  306. y: 5,
  307. height: 35,
  308. width: 35,
  309. img: "/static/images/warning-circle.svg"
  310. }
  311. });
  312. }
  313. const hasChildren = cfg.children && cfg.children.length > 0;
  314. if (hasChildren) {
  315. nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 164, 19);
  316. }
  317. return container;
  318. },
  319. afterDraw: nodeBasicMethod.afterDraw,
  320. setState: nodeBasicMethod.setState
  321. },
  322. "single-shape"
  323. );
  324. /* 复杂节点 */
  325. G6.registerNode(
  326. TREE_NODE,
  327. {
  328. drawShape: (cfg, group) => {
  329. const config = getNodeConfig(cfg);
  330. const isRoot = cfg.type === "root";
  331. const nodeError = cfg.nodeError;
  332. /* 最外面的大矩形 */
  333. const container = nodeBasicMethod.createNodeBox(
  334. group,
  335. config,
  336. 243,
  337. 64,
  338. isRoot
  339. );
  340. if (cfg.type !== "root") {
  341. /* 上边的 type */
  342. group.addShape("text", {
  343. attrs: {
  344. text: cfg.type,
  345. x: 3,
  346. y: -10,
  347. fontSize: 12,
  348. textAlign: "left",
  349. textBaseline: "middle",
  350. fill: "rgba(0,0,0,0.65)"
  351. }
  352. });
  353. }
  354. let ipWidth = 0;
  355. if (cfg.ip) {
  356. /* ip start */
  357. /* ipBox */
  358. const ipRect = group.addShape("rect", {
  359. attrs: {
  360. fill: nodeError ? null : "#FFF",
  361. stroke: nodeError ? "rgba(255,255,255,0.65)" : null,
  362. radius: 2,
  363. cursor: "pointer"
  364. }
  365. });
  366. /* ip */
  367. const ipText = group.addShape("text", {
  368. attrs: {
  369. text: cfg.ip,
  370. x: 0,
  371. y: 19,
  372. fontSize: 12,
  373. textAlign: "left",
  374. textBaseline: "middle",
  375. fill: nodeError ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.65)",
  376. cursor: "pointer"
  377. }
  378. });
  379. const ipBBox = ipText.getBBox();
  380. /* ip 的文字总是距离右边 12px */
  381. ipText.attr({
  382. x: 224 - 12 - ipBBox.width
  383. });
  384. /* ipBox */
  385. ipRect.attr({
  386. x: 224 - 12 - ipBBox.width - 4,
  387. y: ipBBox.minY - 5,
  388. width: ipBBox.width + 8,
  389. height: ipBBox.height + 10
  390. });
  391. /* 在 IP 元素上面覆盖一层透明层,方便监听 hover 事件 */
  392. group.addShape("rect", {
  393. attrs: {
  394. stroke: "",
  395. cursor: "pointer",
  396. x: 224 - 12 - ipBBox.width - 4,
  397. y: ipBBox.minY - 5,
  398. width: ipBBox.width + 8,
  399. height: ipBBox.height + 10,
  400. fill: "#fff",
  401. opacity: 0
  402. },
  403. className: "ip-box"
  404. });
  405. /* copyIpLine */
  406. group.addShape("rect", {
  407. attrs: {
  408. x: 194,
  409. y: 7,
  410. width: 1,
  411. height: 24,
  412. fill: "#E3E6E8",
  413. opacity: 0
  414. },
  415. className: "ip-cp-line"
  416. });
  417. /* copyIpBG */
  418. group.addShape("rect", {
  419. attrs: {
  420. x: 195,
  421. y: 8,
  422. width: 22,
  423. height: 22,
  424. fill: "#FFF",
  425. cursor: "pointer",
  426. opacity: 0
  427. },
  428. className: "ip-cp-bg"
  429. });
  430. /* copyIpIcon */
  431. group.addShape("image", {
  432. attrs: {
  433. x: 200,
  434. y: 13,
  435. height: 12,
  436. width: 10,
  437. img: "https://os.alipayobjects.com/rmsportal/DFhnQEhHyPjSGYW.png",
  438. cursor: "pointer",
  439. opacity: 0
  440. },
  441. className: "ip-cp-icon"
  442. });
  443. /* 放一个透明的矩形在 icon 区域上,方便监听点击 */
  444. group.addShape("rect", {
  445. attrs: {
  446. x: 195,
  447. y: 8,
  448. width: 22,
  449. height: 22,
  450. fill: "#FFF",
  451. cursor: "pointer",
  452. opacity: 0
  453. },
  454. className: "ip-cp-box",
  455. tooltip: "复制IP"
  456. });
  457. const ipRectBBox = ipRect.getBBox();
  458. ipWidth = ipRectBBox.width;
  459. /* ip end */
  460. }
  461. /* name */
  462. const nameText = group.addShape("text", {
  463. attrs: {
  464. text: cfg.name,
  465. x: 19,
  466. y: 19,
  467. fontSize: 14,
  468. fontWeight: 700,
  469. textAlign: "left",
  470. textBaseline: "middle",
  471. fill: config.fontColor,
  472. cursor: "pointer"
  473. }
  474. // tooltip: cfg.name,
  475. });
  476. /* 根据 IP 的长度计算出 剩下的 留给 name 的长度! */
  477. /* 修复 nameText 超长 */
  478. fittingString(nameText, cfg.name, 224 - ipWidth - 20);
  479. /* 下面的文字 */
  480. const remarkText = group.addShape("text", {
  481. attrs: {
  482. text: cfg.keyInfo,
  483. x: 19,
  484. y: 45,
  485. fontSize: 14,
  486. textAlign: "left",
  487. textBaseline: "middle",
  488. fill: config.fontColor,
  489. cursor: "pointer"
  490. }
  491. // className: 'keyInfo',
  492. // tooltip: cfg.keyInfo,
  493. });
  494. fittingString(remarkText, cfg.keyInfo, 204);
  495. if (nodeError) {
  496. group.addShape("image", {
  497. attrs: {
  498. x: 191,
  499. y: 32,
  500. height: 35,
  501. width: 35,
  502. img: "/static/images/warning-circle.svg"
  503. }
  504. });
  505. }
  506. const hasChildren = cfg.children && cfg.children.length > 0;
  507. if (hasChildren) {
  508. nodeBasicMethod.createNodeMarker(group, cfg.collapsed, 236, 32);
  509. }
  510. return container;
  511. },
  512. afterDraw: nodeBasicMethod.afterDraw,
  513. setState: nodeBasicMethod.setState
  514. },
  515. "single-shape"
  516. );
  517. /* 是否显示 sofarouter,通过透明度来控制 */
  518. G6.registerEdge(
  519. "tree-edge",
  520. {
  521. draw(cfg, group) {
  522. const targetNode = cfg.targetNode.getModel();
  523. const edgeError = !!targetNode.edgeError;
  524. const startPoint = cfg.startPoint;
  525. const endPoint = cfg.endPoint;
  526. const controlPoints = this.getControlPoints(cfg);
  527. let points = [startPoint]; // 添加起始点
  528. // 添加控制点
  529. if (controlPoints) {
  530. points = points.concat(controlPoints);
  531. }
  532. // 添加结束点
  533. points.push(endPoint);
  534. const path = this.getPath(points);
  535. group.addShape("path", {
  536. attrs: {
  537. path,
  538. lineWidth: 12,
  539. stroke: edgeError
  540. ? "rgba(245,34,45,0.05)"
  541. : "rgba(47,84,235,0.05)",
  542. opacity: 0,
  543. zIndex: 0
  544. },
  545. className: "line-bg"
  546. });
  547. const keyShape = group.addShape("path", {
  548. attrs: {
  549. path,
  550. lineWidth: 1,
  551. stroke: edgeError ? "#FF7875" : "rgba(0,0,0,0.25)",
  552. zIndex: 1,
  553. lineAppendWidth: 12
  554. },
  555. edgeError: !!edgeError
  556. });
  557. /* 连接线的中间点 */
  558. const centerPoint = {
  559. x: startPoint.x + (endPoint.x - startPoint.x) / 2,
  560. y: startPoint.y + (endPoint.y - startPoint.y) / 2
  561. };
  562. const textRect = group.addShape("rect", {
  563. attrs: {
  564. fill: "#FFF1F0",
  565. radius: 2,
  566. cursor: "pointer",
  567. opacity: 1
  568. },
  569. /* sofarouter 需要 class,以便控制 显示隐藏*/
  570. className: SOFAROUTER_RECT_CLASS
  571. });
  572. const text = group.addShape("text", {
  573. attrs: {
  574. text: "edge-label",
  575. x: 0,
  576. y: 0,
  577. fontSize: 12,
  578. textAlign: "left",
  579. textBaseline: "middle",
  580. fill: "#F5222D",
  581. opacity: 1
  582. },
  583. /* sofarouter 需要 class,以便控制 显示隐藏*/
  584. className: SOFAROUTER_TEXT_CLASS
  585. });
  586. const textBBox = text.getBBox();
  587. /* text 的位置 */
  588. text.attr({
  589. x: centerPoint.x - textBBox.width / 2,
  590. y: centerPoint.y
  591. });
  592. /* text 的框框 */
  593. textRect.attr({
  594. x: centerPoint.x - textBBox.width / 2 - 4,
  595. y: centerPoint.y - textBBox.height / 2 - 5,
  596. width: textBBox.width + 8,
  597. height: textBBox.height + 10
  598. });
  599. return keyShape;
  600. },
  601. /* 操作 线 的背景色显示隐藏 */
  602. afterDraw: (cfg, group) => {
  603. /* 背景色 */
  604. const lineBG = group.get("children")[0]; // 顺序根据 draw 时确定
  605. /* 线条 */
  606. const line = group.get("children")[1];
  607. line.on("mouseenter", () => {
  608. lineBG.attr("opacity", "1");
  609. /* 线条如果在没有错误的情况下,在 hover 时候,是需要变成蓝色的 */
  610. if (!line.get("edgeError")) {
  611. line.attr("stroke", "#2F54EB");
  612. }
  613. graph.get("canvas").draw();
  614. });
  615. line.on("mouseleave", () => {
  616. lineBG.attr("opacity", "0");
  617. if (!line.get("edgeError")) {
  618. line.attr("stroke", "rgba(0,0,0,0.25)");
  619. }
  620. graph.get("canvas").draw();
  621. });
  622. },
  623. setState: (name, value, item) => {
  624. const group = item.getContainer();
  625. const childrens = group.get("children");
  626. graph.setAutoPaint(true);
  627. if (name === "emptiness") {
  628. if (value) {
  629. childrens.forEach(shape => {
  630. if (shape.get("className") === "line-bg") {
  631. return;
  632. }
  633. shape.attr("opacity", 0.4);
  634. });
  635. } else {
  636. childrens.forEach(shape => {
  637. if (shape.get("className") === "line-bg") {
  638. return;
  639. }
  640. shape.attr("opacity", 1);
  641. });
  642. }
  643. }
  644. graph.setAutoPaint(true);
  645. },
  646. update: null
  647. },
  648. "cubic-horizontal"
  649. );
  650. G6.registerBehavior("three-finger-drag-canvas", {
  651. getEvents() {
  652. return {
  653. "canvas:dragstart": "onDragStart",
  654. "canvas:drag": "onDrag",
  655. "canvas:dragend": "onDragEnd"
  656. };
  657. },
  658. onDragStart: ev => {
  659. ev.preventDefault();
  660. this.dragDx = ev.x;
  661. this.dragDy = ev.y;
  662. },
  663. onDrag: ev => {
  664. ev.preventDefault();
  665. translate(this.dragDx - ev.x, this.dragDy - ev.y);
  666. },
  667. onDragEnd: ev => {
  668. ev.preventDefault();
  669. }
  670. });
  671. G6.registerBehavior("double-finger-drag-canvas", {
  672. getEvents() {
  673. return {
  674. wheel: "onWheel"
  675. };
  676. },
  677. onWheel: ev => {
  678. if (ev.ctrlKey) {
  679. const canvas = graph.get("canvas");
  680. const pixelRatio = canvas.get("pixelRatio");
  681. const point = canvas.getPointByClient(ev.clientX, ev.clientY);
  682. let ratio = graph.getZoom();
  683. if (ev.wheelDelta > 0) {
  684. ratio = ratio + ratio * 0.05;
  685. } else {
  686. ratio = ratio - ratio * 0.05;
  687. }
  688. graph.zoomTo(ratio, {
  689. x: point.x / pixelRatio,
  690. y: point.y / pixelRatio
  691. });
  692. } else {
  693. const x = ev.deltaX || ev.movementX;
  694. const y = ev.deltaY || ev.movementY;
  695. translate(x, y);
  696. }
  697. ev.preventDefault();
  698. }
  699. });
  700. /*G6.registerBehavior("tooltip", {
  701. getEvents() {
  702. return {
  703. "node:mousemove": "onMouseMove",
  704. "node:mouseeleave": "onMouseLeave"
  705. };
  706. },
  707. onMouseMove: ev => {
  708. const {
  709. tooltip: { visible }
  710. } = this.state;
  711. const tooltip = ev.target.get("tooltip");
  712. const group = ev.item.get("group");
  713. if (tooltip && !visible) {
  714. const bbox = ev.target.getBBox();
  715. const matrix = group.getMatrix();
  716. const zoom = graph.getZoom();
  717. this.showTooltip(
  718. tooltip,
  719. matrix[6] + bbox.x + bbox.width / 2,
  720. matrix[7] + bbox.y - TIP_HEIGHT / zoom
  721. );
  722. }
  723. if (!tooltip && visible) {
  724. this.closeTooptip();
  725. }
  726. },
  727. onMouseLeave: () => {
  728. const {
  729. tooltip: { visible }
  730. } = this.state;
  731. if (visible) {
  732. this.closeTooptip();
  733. }
  734. }
  735. });*/
  736. const minimap = new Minimap({
  737. size: [184, 124],
  738. className: "minimap",
  739. type: 'delegate'
  740. });
  741. let selectedItem;
  742. graph = new G6.TreeGraph({
  743. container: "mountNode",
  744. width: CANVAS_WIDTH,
  745. height: CANVAS_HEIGHT,
  746. plugins: [minimap],
  747. modes: {
  748. default: [
  749. {
  750. type: "collapse-expand",
  751. shouldUpdate: function shouldUpdate(e) {
  752. /* 点击 node 禁止展开收缩 */
  753. if (e.target.get("className") !== "collapse-icon") {
  754. return false;
  755. }
  756. return true;
  757. },
  758. onChange: function onChange(item, collapsed) {
  759. selectedItem = item;
  760. const icon = item.get("group").findByClassName("collapse-icon");
  761. if (collapsed) {
  762. icon.attr("symbol", EXPAND_ICON);
  763. } else {
  764. icon.attr("symbol", COLLAPSE_ICON);
  765. }
  766. },
  767. animate: {
  768. callback: () => {
  769. debugger;
  770. graph.focusItem(selectedItem);
  771. }
  772. }
  773. },
  774. "double-finger-drag-canvas",
  775. "three-finger-drag-canvas",
  776. { type: "tooltip",
  777. formatText: data => {
  778. return `<div>${data.name}</div>`
  779. }
  780. },
  781. {
  782. type: "drag-canvas",
  783. shouldUpdate: function shouldUpdate() {
  784. return false;
  785. },
  786. shouldEnd: function shouldUpdate() {
  787. return false;
  788. }
  789. }
  790. ]
  791. },
  792. defaultNode: {
  793. shape: TREE_NODE,
  794. anchorPoints: [[0, 0.5], [1, 0.5]]
  795. },
  796. defaultEdge: {
  797. shape: "tree-edge"
  798. },
  799. edgeStyle: {
  800. default: {
  801. stroke: "#A3B1BF"
  802. }
  803. },
  804. layout: {
  805. type: 'compactBox',
  806. direction: "LR",
  807. getId: function getId(d) {
  808. return d.id;
  809. },
  810. getWidth: () => {
  811. return 243;
  812. },
  813. getVGap: function getVGap() {
  814. return 24;
  815. },
  816. getHGap: function getHGap() {
  817. return 50;
  818. }
  819. }
  820. });
  821. function strLen(str) {
  822. var len = 0;
  823. for (var i = 0; i < str.length; i ++) {
  824. if(str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
  825. len ++;
  826. } else {
  827. len += 2;
  828. }
  829. }
  830. return len;
  831. }
  832. function fittingString (c, str, maxWidth) {
  833. const width = strLen(str) * 8;
  834. const ellipsis = "…";
  835. if (width > maxWidth) {
  836. const actualLen = Math.floor((maxWidth - 10) / 8);
  837. c.attr('text', str.substring(0, actualLen) + ellipsis);
  838. c._cfg.tooltip = str;
  839. }
  840. };
  841. function translate(x, y) {
  842. // graph.translate(-x, -y);
  843. let moveX = x;
  844. let moveY = y;
  845. const containerWidth = graph.get("width");
  846. const containerHeight = graph.get("height");
  847. /* 获得当前偏移量*/
  848. const group = graph.get("group");
  849. const bbox = group.getBBox();
  850. const leftTopPoint = graph.getCanvasByPoint(bbox.minX, bbox.minY);
  851. const rightBottomPoint = graph.getCanvasByPoint(bbox.maxX, bbox.maxY);
  852. /* 如果 x 轴在区域内,不允许左右超过100 */
  853. if (x < 0 && leftTopPoint.x - x > LIMIT_OVERFLOW_WIDTH) {
  854. moveX = 0;
  855. }
  856. if (
  857. x > 0 &&
  858. rightBottomPoint.x - x < containerWidth - LIMIT_OVERFLOW_WIDTH
  859. ) {
  860. moveX = 0;
  861. }
  862. if (y < 0 && leftTopPoint.y - y > LIMIT_OVERFLOW_HEIGHT) {
  863. moveY = 0;
  864. }
  865. if (
  866. y > 0 &&
  867. rightBottomPoint.y - y < containerHeight - LIMIT_OVERFLOW_HEIGHT
  868. ) {
  869. moveY = 0;
  870. }
  871. graph.translate(-moveX, -moveY);
  872. };
  873. function fitView() {
  874. const group = graph.get("group");
  875. const width = graph.get("width"); // 视窗的宽度
  876. const height = graph.get("height"); // 视窗的高度
  877. group.resetMatrix();
  878. // 内容
  879. const bbox = group.getBBox();
  880. // 视窗中间
  881. const viewCenter = {
  882. x: width / 2,
  883. y: height / 2
  884. };
  885. /* 内容的中点 */
  886. const groupCenter = {
  887. x: bbox.x + bbox.width / 2,
  888. y: bbox.y + bbox.height / 2
  889. };
  890. graph.translate(-bbox.x + 16, viewCenter.y - groupCenter.y);
  891. };
  892. function zoomTo(ratio, center) {
  893. const width = graph.get("width"); // 视窗的宽度
  894. const height = graph.get("height"); // 视窗的高度
  895. const viewCenter = {
  896. x: width / 2,
  897. y: height / 2
  898. };
  899. graph.zoomTo(ratio, center || viewCenter);
  900. };
  901. function formatData(data) {
  902. const recursiveTraverse = (node, level = 0) => {
  903. const appName = "testappName";
  904. const keyInfo = "testkeyinfo";
  905. const ip = "111.22.33.44";
  906. const targetNode = {
  907. id: node.key + "",
  908. rpcId: node.rpcId,
  909. level,
  910. type: node.appName === "USER" ? "root" : node.type,
  911. name: appName,
  912. keyInfo: keyInfo || "-",
  913. ip,
  914. nodeError: false,
  915. edgeError: false,
  916. sofarouter: true,
  917. sofarouterError: true,
  918. asyn: false,
  919. origin: node
  920. };
  921. if (node.children) {
  922. targetNode.children = [];
  923. node.children.forEach(item => {
  924. targetNode.children.push(recursiveTraverse(item, level + 1));
  925. });
  926. }
  927. return targetNode;
  928. };
  929. const result = recursiveTraverse(data);
  930. return result;
  931. }
  932. graph.on('beforepaint', () => {
  933. const topLeft = graph.getPointByCanvas(0, 0);
  934. const bottomRight = graph.getPointByCanvas(1000, 600);
  935. graph.getNodes().forEach(node => {
  936. const model = node.getModel();
  937. if (model.x < topLeft.x - 200 || model.x > bottomRight.x || model.y < topLeft.y || model.y > bottomRight.y) {
  938. node.getContainer().hide();
  939. } else {
  940. node.getContainer().show();
  941. }
  942. });
  943. const edges = graph.getEdges();
  944. edges.forEach(edge => {
  945. const sourceNode = edge.get('sourceNode');
  946. const targetNode = edge.get('targetNode');
  947. if (!sourceNode.get('visible') && !targetNode.get('visible')) {
  948. edge.hide();
  949. } else {
  950. edge.show();
  951. }
  952. })
  953. });
  954. $.getJSON('./assets/sourceData.json', data => {
  955. data = formatData(data);
  956. graph.data(data);
  957. graph.render();
  958. graph.fitView();
  959. });
  960. </script>
  961. </body>
  962. </html>