From 26d0a8d6810ac6ef27597d1235ca3538fc39eece Mon Sep 17 00:00:00 2001 From: Fine Date: Mon, 8 Jan 2024 16:05:56 +0800 Subject: [PATCH] feat: update layout --- .../related/topology/components/Graph.vue | 120 +-------------- .../components/{Map.vue => HierarchyMap.vue} | 140 +----------------- .../topology/components/utils/layout.ts | 112 ++++++++++++++ 3 files changed, 121 insertions(+), 251 deletions(-) rename src/views/dashboard/related/topology/components/{Map.vue => HierarchyMap.vue} (76%) diff --git a/src/views/dashboard/related/topology/components/Graph.vue b/src/views/dashboard/related/topology/components/Graph.vue index d7a34112..e45bc44e 100644 --- a/src/views/dashboard/related/topology/components/Graph.vue +++ b/src/views/dashboard/related/topology/components/Graph.vue @@ -136,7 +136,7 @@ limitations under the License. --> height="600px" > @@ -162,10 +162,10 @@ limitations under the License. --> import { aggregation } from "@/hooks/useMetricsProcessor"; import icons from "@/assets/img/icons"; import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor"; - import { layout, circleIntersection, computeCallPos } from "./utils/layout"; + import { layout, computeLevels, changeNode } from "./utils/layout"; import zoom from "../../components/utils/zoom"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; - import Map from "./Map.vue"; + import HierarchyMap from "./HierarchyMap.vue"; /*global Nullable, defineProps */ const props = defineProps({ @@ -194,7 +194,7 @@ limitations under the License. --> const topologyLayout = ref({}); const tooltip = ref>(null); const graphWidth = ref(100); - const currentNode = ref>(); + const currentNode = ref>(null); const diff = computed(() => [(width.value - graphWidth.value - 130) / 2, 100]); const hierarchyRelated = ref(false); const radius = 8; @@ -253,107 +253,19 @@ limitations under the License. --> setNodeTools(settings.value.nodeDashboard); } - function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) { - const node = findMostFrequent(calls); - const nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => { - if (a.name.toLowerCase() < b.name.toLowerCase()) { - return -1; - } - if (a.name.toLowerCase() > b.name.toLowerCase()) { - return 1; - } - return 0; - }); - const index = nodes.findIndex((n: Node) => n.type === "USER"); - let key = index; - if (index < 0) { - key = nodes.findIndex((n: Node) => n.id === node.id); - } - levels.push([nodes[key]]); - nodes.splice(key, 1); - for (const level of levels) { - const a = []; - for (const l of level) { - for (const n of calls) { - if (n.target === l.id) { - const i = nodes.findIndex((d: Node) => d.id === n.source); - if (i > -1) { - a.push(nodes[i]); - nodes.splice(i, 1); - } - } - if (n.source === l.id) { - const i = nodes.findIndex((d: Node) => d.id === n.target); - if (i > -1) { - a.push(nodes[i]); - nodes.splice(i, 1); - } - } - } - } - if (a.length) { - levels.push(a); - } - } - if (nodes.length) { - const ids = nodes.map((d: Node) => d.id); - const links = calls.filter((item: Call) => ids.includes(item.source) || ids.includes(item.target)); - const list = computeLevels(links, nodes, []); - levels = list.map((subArrayA, index) => subArrayA.concat(levels[index])); - } - return levels; - } - function draw() { const levels = computeLevels(topologyStore.calls, topologyStore.nodes, []); topologyLayout.value = layout(levels, topologyStore.calls, radius); graphWidth.value = topologyLayout.value.layout.width; const drag: any = d3.drag().on("drag", (d: { x: number; y: number }) => { - moveNode(d); + topologyLayout.value.calls = changeNode(d, currentNode.value, topologyLayout.value, radius); }); setTimeout(() => { d3.selectAll(".topo-node").call(drag); }, 1000); } - function moveNode(d: { x: number; y: number }) { - if (!currentNode.value) { - return; - } - for (const node of topologyLayout.value.nodes) { - if (node.id === currentNode.value.id) { - node.x = d.x; - node.y = d.y; - } - } - for (const call of topologyLayout.value.calls) { - if (call.sourceObj.id === currentNode.value.id) { - call.sourceObj.x = d.x; - call.sourceObj.y = d.y; - } - if (call.targetObj.id === currentNode.value.id) { - call.targetObj.x = d.x; - call.targetObj.y = d.y; - } - if (call.targetObj.id === currentNode.value.id || call.sourceObj.id === currentNode.value.id) { - const pos: any = circleIntersection( - call.sourceObj.x, - call.sourceObj.y, - radius, - call.targetObj.x, - call.targetObj.y, - radius, - ); - call.sourceX = pos[0].x; - call.sourceY = pos[0].y; - call.targetX = pos[1].x; - call.targetY = pos[1].y; - } - } - topologyLayout.value.calls = computeCallPos(topologyLayout.value.calls, radius); - } - function startMoveNode(event: MouseEvent, d: Node) { event.stopPropagation(); currentNode.value = d; @@ -363,28 +275,6 @@ limitations under the License. --> currentNode.value = null; } - function findMostFrequent(arr: Call[]) { - let count: any = {}; - let maxCount = 0; - let maxItem = null; - - for (let i = 0; i < arr.length; i++) { - let item = arr[i]; - count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1; - if (count[item.sourceObj.id] > maxCount) { - maxCount = count[item.sourceObj.id]; - maxItem = item.sourceObj; - } - count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1; - if (count[item.targetObj.id] > maxCount) { - maxCount = count[item.targetObj.id]; - maxItem = item.targetObj; - } - } - - return maxItem; - } - async function initLegendMetrics() { if (!topologyStore.nodes.length) { return; diff --git a/src/views/dashboard/related/topology/components/Map.vue b/src/views/dashboard/related/topology/components/HierarchyMap.vue similarity index 76% rename from src/views/dashboard/related/topology/components/Map.vue rename to src/views/dashboard/related/topology/components/HierarchyMap.vue index 0396dfe2..c5d1419e 100644 --- a/src/views/dashboard/related/topology/components/Map.vue +++ b/src/views/dashboard/related/topology/components/HierarchyMap.vue @@ -56,15 +56,6 @@ limitations under the License. --> stroke="#97B0F8" marker-end="url(#arrow)" /> - @@ -106,7 +97,7 @@ limitations under the License. --> import { aggregation } from "@/hooks/useMetricsProcessor"; import icons from "@/assets/img/icons"; import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor"; - import { layout, circleIntersection, computeCallPos } from "./utils/layout"; + import { layout, changeNode, computeLevels } from "./utils/layout"; import zoom from "../../components/utils/zoom"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; @@ -133,7 +124,7 @@ limitations under the License. --> const topologyLayout = ref({}); const popover = ref>(null); const graphWidth = ref(100); - const currentNode = ref>(); + const currentNode = ref>(null); const diff = computed(() => [(width.value - graphWidth.value) / 2, 0]); const radius = 8; @@ -180,107 +171,19 @@ limitations under the License. --> popover.value = d3.select("#popover"); } - function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) { - const node = findMostFrequent(calls); - const nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => { - if (a.name.toLowerCase() < b.name.toLowerCase()) { - return -1; - } - if (a.name.toLowerCase() > b.name.toLowerCase()) { - return 1; - } - return 0; - }); - const index = nodes.findIndex((n: Node) => n.type === "USER"); - let key = index; - if (index < 0) { - key = nodes.findIndex((n: Node) => n.id === node.id); - } - levels.push([nodes[key]]); - nodes.splice(key, 1); - for (const level of levels) { - const a = []; - for (const l of level) { - for (const n of calls) { - if (n.target === l.id) { - const i = nodes.findIndex((d: Node) => d.id === n.source); - if (i > -1) { - a.push(nodes[i]); - nodes.splice(i, 1); - } - } - if (n.source === l.id) { - const i = nodes.findIndex((d: Node) => d.id === n.target); - if (i > -1) { - a.push(nodes[i]); - nodes.splice(i, 1); - } - } - } - } - if (a.length) { - levels.push(a); - } - } - if (nodes.length) { - const ids = nodes.map((d: Node) => d.id); - const links = calls.filter((item: Call) => ids.includes(item.source) || ids.includes(item.target)); - const list = computeLevels(links, nodes, []); - levels = list.map((subArrayA, index) => subArrayA.concat(levels[index])); - } - return levels; - } - function draw() { const levels = computeLevels(topologyStore.calls, topologyStore.nodes, []); topologyLayout.value = layout(levels, topologyStore.calls, radius); graphWidth.value = topologyLayout.value.layout.width; const drag: any = d3.drag().on("drag", (d: { x: number; y: number }) => { - moveNode(d); + topologyLayout.value.calls = changeNode(d, currentNode.value, topologyLayout.value, radius); }); setTimeout(() => { d3.selectAll(".topo-node").call(drag); }, 1000); } - function moveNode(d: { x: number; y: number }) { - if (!currentNode.value) { - return; - } - for (const node of topologyLayout.value.nodes) { - if (node.id === currentNode.value.id) { - node.x = d.x; - node.y = d.y; - } - } - for (const call of topologyLayout.value.calls) { - if (call.sourceObj.id === currentNode.value.id) { - call.sourceObj.x = d.x; - call.sourceObj.y = d.y; - } - if (call.targetObj.id === currentNode.value.id) { - call.targetObj.x = d.x; - call.targetObj.y = d.y; - } - if (call.targetObj.id === currentNode.value.id || call.sourceObj.id === currentNode.value.id) { - const pos: any = circleIntersection( - call.sourceObj.x, - call.sourceObj.y, - radius, - call.targetObj.x, - call.targetObj.y, - radius, - ); - call.sourceX = pos[0].x; - call.sourceY = pos[0].y; - call.targetX = pos[1].x; - call.targetY = pos[1].y; - } - } - topologyLayout.value.calls = computeCallPos(topologyLayout.value.calls, radius); - } - function startMoveNode(event: MouseEvent, d: Node) { event.stopPropagation(); currentNode.value = d; @@ -290,28 +193,6 @@ limitations under the License. --> currentNode.value = null; } - function findMostFrequent(arr: Call[]) { - let count: any = {}; - let maxCount = 0; - let maxItem = null; - - for (let i = 0; i < arr.length; i++) { - let item = arr[i]; - count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1; - if (count[item.sourceObj.id] > maxCount) { - maxCount = count[item.sourceObj.id]; - maxItem = item.sourceObj; - } - count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1; - if (count[item.targetObj.id] > maxCount) { - maxCount = count[item.targetObj.id]; - maxItem = item.targetObj; - } - } - - return maxItem; - } - async function initLegendMetrics() { if (!topologyStore.nodes.length) { return; @@ -398,19 +279,11 @@ limitations under the License. --> .style("visibility", "visible") .html(tipHtml); } - function showLinkTip(event: MouseEvent, data: Call) { - const html = `
${t("detectPoint")}:${data.detectPoints.join(" | ")}
`; - - popover.value - .style("top", event.offsetY + "px") - .style("left", event.offsetX + "px") - .style("visibility", "visible") - .html(html); - } function hideTip() { popover.value.style("visibility", "hidden"); } + function handleNodeClick(event: MouseEvent, d: Node & { x: number; y: number }) { event.stopPropagation(); hideTip(); @@ -425,12 +298,7 @@ limitations under the License. --> window.open(routeUrl.href, "_blank"); dashboardStore.setEntity(origin); } - function handleGoAlerting() { - const path = `/alerting`; - const routeUrl = router.resolve({ path }); - window.open(routeUrl.href, "_blank"); - } async function getTopology() { const ids = selectorStore.services.map((d: Service) => d.id); const serviceIds = dashboardStore.entity === EntityType[0].value ? [selectorStore.currentService.id] : ids; diff --git a/src/views/dashboard/related/topology/components/utils/layout.ts b/src/views/dashboard/related/topology/components/utils/layout.ts index 52b35e5b..c42a4ded 100644 --- a/src/views/dashboard/related/topology/components/utils/layout.ts +++ b/src/views/dashboard/related/topology/components/utils/layout.ts @@ -120,3 +120,115 @@ export function circleIntersection(ax: number, ay: number, ar: number, bx: numbe { x: gx, y: gy }, ]; } +function findMostFrequent(arr: Call[]) { + const count: any = {}; + let maxCount = 0; + let maxItem = null; + + for (let i = 0; i < arr.length; i++) { + const item = arr[i]; + count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1; + if (count[item.sourceObj.id] > maxCount) { + maxCount = count[item.sourceObj.id]; + maxItem = item.sourceObj; + } + count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1; + if (count[item.targetObj.id] > maxCount) { + maxCount = count[item.targetObj.id]; + maxItem = item.targetObj; + } + } + + return maxItem; +} +export function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) { + const node = findMostFrequent(calls); + const nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => { + if (a.name.toLowerCase() < b.name.toLowerCase()) { + return -1; + } + if (a.name.toLowerCase() > b.name.toLowerCase()) { + return 1; + } + return 0; + }); + const index = nodes.findIndex((n: Node) => n.type === "USER"); + let key = index; + if (index < 0) { + key = nodes.findIndex((n: Node) => n.id === node.id); + } + levels.push([nodes[key]]); + nodes.splice(key, 1); + for (const level of levels) { + const a = []; + for (const l of level) { + for (const n of calls) { + if (n.target === l.id) { + const i = nodes.findIndex((d: Node) => d.id === n.source); + if (i > -1) { + a.push(nodes[i]); + nodes.splice(i, 1); + } + } + if (n.source === l.id) { + const i = nodes.findIndex((d: Node) => d.id === n.target); + if (i > -1) { + a.push(nodes[i]); + nodes.splice(i, 1); + } + } + } + } + if (a.length) { + levels.push(a); + } + } + if (nodes.length) { + const ids = nodes.map((d: Node) => d.id); + const links = calls.filter((item: Call) => ids.includes(item.source) || ids.includes(item.target)); + const list = computeLevels(links, nodes, []); + levels = list.map((subArrayA, index) => subArrayA.concat(levels[index])); + } + return levels; +} +export function changeNode( + d: { x: number; y: number }, + currentNode: Nullable, + topologyLayout: any, + radius: number, +) { + if (!currentNode) { + return; + } + for (const node of topologyLayout.nodes) { + if (node.id === currentNode.id) { + node.x = d.x; + node.y = d.y; + } + } + for (const call of topologyLayout.calls) { + if (call.sourceObj.id === currentNode.id) { + call.sourceObj.x = d.x; + call.sourceObj.y = d.y; + } + if (call.targetObj.id === currentNode.id) { + call.targetObj.x = d.x; + call.targetObj.y = d.y; + } + if (call.targetObj.id === currentNode.id || call.sourceObj.id === currentNode.id) { + const pos: any = circleIntersection( + call.sourceObj.x, + call.sourceObj.y, + radius, + call.targetObj.x, + call.targetObj.y, + radius, + ); + call.sourceX = pos[0].x; + call.sourceY = pos[0].y; + call.targetX = pos[1].x; + call.targetY = pos[1].y; + } + } + return computeCallPos(topologyLayout.value.calls, radius); +}