提交 f8b5f7aa authored 作者: yanpeng's avatar yanpeng

关系图

上级 8e94a51d
<template>
<div class="graph-chart-wrapper" id="graph">
</div>
<div class="graph-chart-wrapper" id="graph"></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount } from 'vue';
import setChart from '@/utils/setChart';
import getGraphChart from './graphChart';
import { onMounted, onBeforeUnmount, watch } from "vue";
import setChart from "@/utils/setChart";
import getGraphChart from "./graphChart";
const emits = defineEmits(["handleClickNode"])
const emits = defineEmits(["handleClickNode"]);
const props = defineProps({
nodes: {
type: Array,
default: []
},
links: {
type: Array,
default: []
},
layoutType: {
type: String,
default: 'force'
},
width: {
type: String,
default: 'force'
},
height: {
type: String,
default: 'force'
}
})
nodes: {
type: Array,
default: []
},
links: {
type: Array,
default: []
},
layoutType: {
type: String,
default: "force"
},
width: {
type: String,
default: "force"
},
height: {
type: String,
default: "force"
}
});
let chart = null
let chart = null;
onMounted(() => {
const graph = getGraphChart(props.nodes, props.links, props.layoutType)
chart = setChart(graph, 'graph')
chart.on("click", (event) => { emits("handleClickNode", event) })
})
const graph = getGraphChart(props.nodes, props.links, props.layoutType);
chart = setChart(graph, "graph");
chart.on("click", event => {
emits("handleClickNode", event);
});
});
onBeforeUnmount(() => {
chart.off("click")
chart.dispose()
})
chart.off("click");
chart.dispose();
});
watch(
() => [props.nodes, props.links],
() => {
if (chart) {
const graph = getGraphChart(props.nodes, props.links, props.layoutType);
chart.setOption(graph, true);
}
},
{ deep: true }
);
</script>
<style lang="scss" scoped>
.graph-chart-wrapper {
width: 100%;
height: 100%;
// width: 800px;
// height: 500px;
width: 100%;
height: 100%;
// width: 800px;
// height: 500px;
}
</style>
\ No newline at end of file
</style>
......@@ -107,22 +107,185 @@ const initGraph = (layoutType = 1) => {
});
};
// const initNormalGraph = (layoutType, width, height) => {
// const data = processGraphData(props.graphData);
// console.log("初始数据", props.graphData);
// if (!data.nodes || data.nodes.length === 0) return;
// const layout = {
// type: "none",
// center: [width / 2, height / 2],
// preventOverlap: true,
// nodeSpacing: 80,
// linkDistance: 250,
// nodeStrength: -800,
// edgeStrength: 0.1,
// collideStrength: 0.8,
// alphaDecay: 0.01,
// alphaMin: 0.001
// };
// graphInstance.value = new G6.Graph({
// container: containerRef.value,
// width,
// height,
// fitView: true,
// fitViewPadding: 100,
// fitCenter: true,
// animate: true,
// animateCfg: {
// duration: 300,
// easing: "easeLinear"
// },
// minZoom: 0.1,
// maxZoom: 10,
// modes: {
// default: [
// "drag-canvas",
// "zoom-canvas",
// "drag-node",
// {
// type: "activate-relations",
// trigger: "mouseenter",
// resetSelected: true
// }
// ]
// },
// layout,
// defaultNode: {
// type: "image",
// size: 40,
// clipCfg: {
// show: true,
// type: "circle",
// r: 20
// },
// labelCfg: {
// position: "bottom",
// offset: 10,
// style: {
// fill: "#333",
// fontSize: 11,
// fontFamily: "Microsoft YaHei",
// textAlign: "center",
// background: {
// fill: "rgba(255, 255, 255, 0.95)",
// padding: [4, 6, 4, 6],
// radius: 4
// }
// }
// }
// // 注意:节点边框样式在 processGraphData 中单独设置,不在这里设置
// },
// defaultEdge: {
// type: "quadratic",
// style: {
// stroke: "red",
// lineWidth: 3,
// opacity: 0.9,
// shadowColor: "rgba(231, 243, 255, 1)",
// shadowBlur: 4,
// endArrow: {
// path: "M 0,0 L 12,6 L 12,-6 Z",
// fill: "#5B8FF9"
// }
// },
// labelCfg: {
// autoRotate: true,
// style: {
// fill: "rgba(137, 193, 255, 1)",
// fontSize: 10,
// fontFamily: "Microsoft YaHei",
// background: {
// fill: "rgba(231, 243, 255, 1)",
// padding: [2, 4, 2, 4],
// radius: 5
// }
// }
// }
// },
// nodeStateStyles: {
// active: {
// shadowColor: "#1459BB",
// shadowBlur: 15,
// stroke: "#1459BB",
// lineWidth: 3
// },
// inactive: {
// opacity: 0.3
// }
// },
// edgeStateStyles: {
// active: {
// stroke: "#1459BB",
// lineWidth: 4
// },
// inactive: {
// opacity: 0.15
// }
// }
// });
// graphInstance.value.data(data);
// graphInstance.value.render();
// bindGraphEvents();
// };
const initNormalGraph = (layoutType, width, height) => {
const data = processGraphData(props.graphData);
console.log("初始数据", props.graphData);
if (!data.nodes || data.nodes.length === 0) return;
// 中心节点坐标
const centerX = width / 2;
const centerY = height / 2;
const upperY = centerY - 200; // 上方节点 Y 坐标
const lowerY = centerY + 200; // 下方节点 Y 坐标
const nodeSpacing = 100; // 节点水平间距
// 分离中心节点和其他节点
const centerNode = data.nodes.find(n => n.isCenter);
const otherNodes = data.nodes.filter(n => !n.isCenter);
const totalNodes = otherNodes.length;
const upperCount = Math.ceil(totalNodes / 2); // 向上取整,上方多一个
const lowerCount = totalNodes - upperCount;
// 为上方节点分配坐标
const upperNodes = otherNodes.slice(0, upperCount);
upperNodes.forEach((node, index) => {
const totalWidth = (upperCount - 1) * nodeSpacing;
const startX = centerX - totalWidth / 2;
node.x = startX + index * nodeSpacing;
node.y = upperY;
// 固定位置,防止力导向布局移动
node.fx = node.x;
node.fy = node.y;
});
// 为下方节点分配坐标
const lowerNodes = otherNodes.slice(upperCount);
lowerNodes.forEach((node, index) => {
const totalWidth = (lowerCount - 1) * nodeSpacing;
const startX = centerX - totalWidth / 2;
node.x = startX + index * nodeSpacing;
node.y = lowerY;
// 固定位置,防止力导向布局移动
node.fx = node.x;
node.fy = node.y;
});
// 设置中心节点坐标
if (centerNode) {
centerNode.x = centerX;
centerNode.y = centerY;
// 固定中心节点位置
centerNode.fx = centerX;
centerNode.fy = centerY;
}
const layout = {
type: "force",
center: [width / 2, height / 2],
preventOverlap: true,
nodeSpacing: 80,
linkDistance: 250,
nodeStrength: -800,
edgeStrength: 0.1,
collideStrength: 0.8,
alphaDecay: 0.01,
alphaMin: 0.001
type: "none", // 使用预设坐标,不进行力导向布局
center: [centerX, centerY]
};
graphInstance.value = new G6.Graph({
......@@ -175,10 +338,9 @@ const initNormalGraph = (layoutType, width, height) => {
}
}
}
// 注意:节点边框样式在 processGraphData 中单独设置,不在这里设置
},
defaultEdge: {
type: "quadratic",
type: "line",
style: {
stroke: "red",
lineWidth: 3,
......@@ -230,7 +392,6 @@ const initNormalGraph = (layoutType, width, height) => {
graphInstance.value.render();
bindGraphEvents();
};
const initCircularGraph = (width, height) => {
const data = processGraphData(props.graphData);
......@@ -307,7 +468,7 @@ const initCircularGraph = (width, height) => {
}
},
defaultEdge: {
type: "quadratic",
type: "line",
style: {
stroke: "#5B8FF9",
lineWidth: 3,
......
......@@ -110,6 +110,7 @@
@node-click="handleNodeClick"
@layout-change="handleLayoutChange"
/>
<!-- <GraphChart :nodes="nodes" :links="links" layoutType="none" /> -->
</div>
</div>
</div>
......@@ -134,6 +135,7 @@ import {
} from "@/api/exportControlV2.0";
import RelationGraph from "./components/RelationGraph.vue";
import AnalysisBox from "@/components/base/boxBackground/analysisBox.vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
const sanRecordId = ref("");
const activeTab = ref(["实体穿透分析"]);
......@@ -157,6 +159,9 @@ const graphData = ref({ nodes: [], links: [] });
const treeData = ref(null);
const selectedNode = ref(null);
const nodes = ref([]);
const links = ref([]);
const singleSanctionEntityEquityData = ref(null);
const singleSanctionEntitySupplyChainData = ref(null);
const singleSanctionEntityList = ref([]);
......@@ -204,6 +209,7 @@ const updateGraphData = () => {
const data =
rightActiveTab.value === "supplyChain" ? singleSanctionEntitySupplyChainData.value : singleSanctionEntityEquityData.value;
console.log("图谱数据 =>", data);
if (!data) return;
const nodes = [];
......@@ -252,8 +258,172 @@ const updateGraphData = () => {
});
graphData.value = { nodes, links };
// nodes.value = nodes;
// links.value = links;
};
const links1 = [
{ source: 1, target: 7, label: { show: true, formatter: "合作" } },
{ source: 2, target: 7, label: { show: true, formatter: "持股" } },
{ source: 3, target: 7, label: { show: true, formatter: "合作" } },
{ source: 4, target: 7, lineStyle: { type: "dashed", color: "#d32f2f" }, label: { show: true, formatter: "从属" } },
{ source: 5, target: 7, label: { show: true, formatter: "合作" } },
{ source: 6, target: 7, label: { show: true, formatter: "持股" } },
{ source: 0, target: 7, label: { show: true, formatter: "持股" } },
{ source: 8, target: 7, label: { show: true, formatter: "合作" } },
{ source: 9, target: 7, lineStyle: { type: "dashed", color: "#d32f2f" }, label: { show: true, formatter: "从属" } },
{ source: 10, target: 7, lineStyle: { type: "dashed", color: "#d32f2f" }, label: { show: true, formatter: "合作" } },
{ source: 11, target: 7, label: { show: true, formatter: "合作" } },
{ source: 12, target: 7, label: { show: true, formatter: "合作" } },
{ source: 13, target: 7, label: { show: true, formatter: "合作" } },
{ source: 14, target: 7, label: { show: true, formatter: "合作" } },
{ source: 15, target: 7, label: { show: true, formatter: "合作", color: "red", borderColor: "red" } }
];
// const updateGraphData = () => {
// const data =
// rightActiveTab.value === "supplyChain" ? singleSanctionEntitySupplyChainData.value : singleSanctionEntityEquityData.value;
// console.log("图谱数据 =>", data);
// if (!data) return;
// const newNodes = [];
// const newLinks = [];
// // 容器尺寸(根据 .right-echarts 的高度 calc(100% - 56px) ≈ 772px)
// const containerWidth = 1000;
// const containerHeight = 700;
// // 中心节点坐标(居中)
// const centerX = containerWidth / 2;
// const centerY = containerHeight / 2;
// // 上下节点分布参数
// const upperY = centerY - 200; // 上方节点 Y 坐标
// const lowerY = centerY + 200; // 下方节点 Y 坐标
// const nodeSpacing = 100; // 节点水平间距
// // 合并所有节点列表(上游 + 下游)
// const allItems = [];
// const parentList = data.parentOrgList || [];
// const childList = data.childrenOrgList || [];
// // 添加上游节点
// parentList.forEach((item, index) => {
// allItems.push({
// ...item,
// linkType: rightActiveTab.value === "supplyChain" ? "供应商" : item.type || "持股",
// direction: "source" // 链接方向:指向中心节点
// });
// });
// // 添加下游节点
// childList.forEach((item, index) => {
// allItems.push({
// ...item,
// linkType: rightActiveTab.value === "supplyChain" ? "客户" : item.description || "投资",
// direction: "target" // 链接方向:从中心节点指出
// });
// });
// // 中心节点
// newNodes.push({
// id: 0,
// name: data.orgName || "中心节点",
// symbol: `image://${companyActive}`,
// symbolSize: 60,
// value: 10,
// isSanctioned: true,
// x: centerX,
// y: centerY
// });
// // 计算上下分配
// const totalNodes = allItems.length;
// const upperCount = Math.ceil(totalNodes / 2); // 向上取整,上方多一个
// const lowerCount = totalNodes - upperCount;
// // 上方节点(前一半)
// const upperItems = allItems.slice(0, upperCount);
// upperItems.forEach((item, index) => {
// const totalWidth = (upperCount - 1) * nodeSpacing;
// const startX = centerX - totalWidth / 2;
// const x = startX + index * nodeSpacing;
// const y = upperY;
// const nodeId = `n-${index}`;
// newNodes.push({
// id: nodeId,
// name: item.name || `节点${index}`,
// symbol: `image://${item.isSanctioned ? companyActive : company}`,
// symbolSize: 40,
// value: 5,
// isSanctioned: item.isSanctioned,
// x,
// y
// });
// // 根据 direction 决定链接方向
// if (item.direction === "source") {
// newLinks.push({
// source: nodeId,
// target: 0,
// name: item.linkType,
// label: { show: true, formatter: item.description }
// });
// } else {
// newLinks.push({
// source: 0,
// target: nodeId,
// name: item.linkType,
// label: { show: true, formatter: item.description }
// });
// }
// });
// // 下方节点(后一半)
// const lowerItems = allItems.slice(upperCount);
// lowerItems.forEach((item, index) => {
// const totalWidth = (lowerCount - 1) * nodeSpacing;
// const startX = centerX - totalWidth / 2;
// const x = startX + index * nodeSpacing;
// const y = lowerY;
// const nodeId = `n-${upperCount + index}`;
// newNodes.push({
// id: nodeId,
// name: item.name || `节点${upperCount + index}`,
// symbol: `image://${item.isSanctioned ? companyActive : company}`,
// symbolSize: 40,
// value: 5,
// isSanctioned: item.isSanctioned,
// x,
// y
// });
// // 根据 direction 决定链接方向
// if (item.direction === "source") {
// newLinks.push({
// source: nodeId,
// target: 0,
// name: item.linkType,
// label: { show: true, formatter: "{c}" }
// });
// } else {
// newLinks.push({
// source: 0,
// target: nodeId,
// name: item.linkType,
// label: { show: true, formatter: "{c}" }
// });
// }
// });
// console.log("最终节点数:", newNodes, "最终链接数:", newLinks, "上方节点:", upperCount, "下方节点:", lowerCount);
// // 更新响应式数据
// nodes.value = newNodes;
// links.value = newLinks;
// };
const updateTreeData = data => {
if (!data) return;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论