初步实现6个圆圈(但连线穿过圆圈了需要优化)

This commit is contained in:
chatgpt-yunju 2025-07-07 18:56:33 +08:00
parent b5acf7652d
commit b00b1293e4
2 changed files with 46 additions and 48 deletions

View File

@ -14,7 +14,6 @@ const containerRef = ref(null);
onMounted(() => { onMounted(() => {
// --- 1. Data Preparation --- // --- 1. Data Preparation ---
// Initial node data (no changes needed here)
const initialNodes = [ const initialNodes = [
{ id: '6010609377', name: '外贸发布BBS', default_avatar: '外贸发布BBS_waimaofabuBBS.png' }, { id: '6010609377', name: '外贸发布BBS', default_avatar: '外贸发布BBS_waimaofabuBBS.png' },
{ id: '6797803070', name: '爱锤盾海桃-霆恩启副', default_avatar: '爱锤盾海桃-霆恩启副_aichuidunhaitao-tingenqifu.png' }, { id: '6797803070', name: '爱锤盾海桃-霆恩启副', default_avatar: '爱锤盾海桃-霆恩启副_aichuidunhaitao-tingenqifu.png' },
@ -37,8 +36,8 @@ onMounted(() => {
{ id: '2548116484', name: '肥_谍_gg', default_avatar: '肥_谍_gg_fei_die_gg.png' }, { id: '2548116484', name: '肥_谍_gg', default_avatar: '肥_谍_gg_fei_die_gg.png' },
{ id: '1854070075', name: '深海一万米', default_avatar: '深海一万米_shenhaiyiwanmi.png' }, { id: '1854070075', name: '深海一万米', default_avatar: '深海一万米_shenhaiyiwanmi.png' },
]; ];
// Map node names to their cluster ID // The mapping of nodes to their clusters remains the same
const clusterMapping = { const clusterMapping = {
'中国海警': 'cluster-0', '十八子91221': 'cluster-0', '大侠啊啊啊啊': 'cluster-0', '中国海警': 'cluster-0', '十八子91221': 'cluster-0', '大侠啊啊啊啊': 'cluster-0',
'苍龙飞天79': 'cluster-1', '平安泸县': 'cluster-1', '新浪军事': 'cluster-1', '环球时报': 'cluster-1', '乐之567': 'cluster-1', 'CGTN记者团': 'cluster-1', '深海一万米': 'cluster-1', '苍龙飞天79': 'cluster-1', '平安泸县': 'cluster-1', '新浪军事': 'cluster-1', '环球时报': 'cluster-1', '乐之567': 'cluster-1', 'CGTN记者团': 'cluster-1', '深海一万米': 'cluster-1',
@ -48,36 +47,29 @@ onMounted(() => {
'肥_谍_gg': 'cluster-5', '肥_谍_gg': 'cluster-5',
}; };
const nodeSize = 50;
const nodes = initialNodes.map(node => ({ const nodes = initialNodes.map(node => ({
...node, ...node,
comboId: clusterMapping[node.name], comboId: clusterMapping[node.name],
label: '', // Labels are disabled for this design label: '',
type: 'image', type: 'image',
img: new URL(`/src/assets/user3/${node.default_avatar}`, import.meta.url).href, img: new URL(`/src/assets/user3/${node.default_avatar}`, import.meta.url).href,
clipCfg: { show: true, type: 'circle', r: 25 }, clipCfg: { show: true, type: 'circle', r: nodeSize / 2 },
}));
// --- Fixed Combo Layout, Styles, and Connections ---
// Define fixed positions, sizes, and styles for each combo based on SwiftUI code and screenshot
const comboLayoutData = {
'cluster-0': { x: 250, y: 220, r: 120, style: { fill: '#FF5E0014', stroke: '#FF5E007A' } }, // Orange
'cluster-1': { x: 500, y: 180, r: 120, style: { fill: '#BA21EB14', stroke: '#BA21EB7A' } }, // Purple
'cluster-2': { x: 700, y: 500, r: 80, style: { fill: '#2191EB29', stroke: '#2191EB' } }, // Blue
'cluster-3': { x: 800, y: 250, r: 137, style: { fill: '#FFD90A14', stroke: '#FFD90A7A' } }, // Yellow
'cluster-4': { x: 180, y: 480, r: 80, style: { fill: '#3DD99414', stroke: '#3DD9947A' } }, // Green
'cluster-5': { x: 450, y: 450, r: 110, style: { fill: '#D93D6B14', stroke: '#D93D6B7A' } }, // Pink/Red
};
const combos = Object.entries(comboLayoutData).map(([id, { x, y, r, style }]) => ({
id,
label: '',
x, y, fx: x, fy: y, // Fix combo positions
r, // Set radius directly
style
})); }));
// Define connections between combos based on the target image // Define Combos with styles and relative sizes. Positions will be calculated by the layout engine.
const combos = [
{ id: 'cluster-0', label: '', r: 90, style: { fill: '#FF5E0014', stroke: '#FF5E007A' } },
{ id: 'cluster-1', label: '', r: 110, style: { fill: '#BA21EB14', stroke: '#BA21EB7A' } },
{ id: 'cluster-2', label: '', r: 80, style: { fill: '#2191EB29', stroke: '#2191EB' } },
{ id: 'cluster-3', label: '', r: 130, style: { fill: '#FFD90A14', stroke: '#FFD90A7A' } },
{ id: 'cluster-4', label: '', r: 75, style: { fill: '#3DD99414', stroke: '#3DD9947A' } },
{ id: 'cluster-5', label: '', r: 100, style: { fill: '#D93D6B14', stroke: '#D93D6B7A' } },
];
// Re-introduce edges to define relationships for the force layout.
// These connections are based on the visual overlaps in the original image.
const comboConnections = [ const comboConnections = [
['cluster-0', 'cluster-1'], ['cluster-0', 'cluster-4'], ['cluster-0', 'cluster-1'], ['cluster-0', 'cluster-4'],
['cluster-1', 'cluster-3'], ['cluster-1', 'cluster-5'], ['cluster-1', 'cluster-3'], ['cluster-1', 'cluster-5'],
@ -86,39 +78,40 @@ onMounted(() => {
['cluster-5', 'cluster-2'], ['cluster-5', 'cluster-2'],
]; ];
// G6 needs edges between nodes, so we find one node from each combo to connect.
const edges = comboConnections.map(([sourceComboId, targetComboId], i) => { const edges = comboConnections.map(([sourceComboId, targetComboId], i) => {
const sourceNode = nodes.find(n => n.comboId === sourceComboId); return { id: `edge-${i}`, source: sourceComboId, target: targetComboId };
const targetNode = nodes.find(n => n.comboId === targetComboId);
return {
id: `edge-${i}`,
source: sourceNode.id,
target: targetNode.id,
};
}); });
const data = { nodes, combos, edges }; const data = { nodes, combos, edges };
// --- 2. G6 Graph Initialization --- // --- 2. G6 Graph Initialization ---
const container = containerRef.value; const container = containerRef.value;
const width = container.scrollWidth; const width = container.scrollWidth || 1200;
const height = container.scrollHeight || 700; const height = container.scrollHeight || 800;
graph = new G6.Graph({ graph = new G6.Graph({
container, container,
width, width,
height, height,
layout: null, //
groupByTypes: false, // combo-to-combo
// --- MODIFICATION: Set preventComboOverlap to true to avoid circle overlaps ---
layout: { layout: {
type: 'comboForce', type: 'comboForce',
gravity: 80, // Increase gravity to pull nodes towards combo centers preventComboOverlap: true, // combo
nodeSpacing: 25, preventNodeOverlap: true, //
preventOverlap: true, linkDistance: 250, //
preventComboOverlap: true, // Should be redundant with fixed combos, but safe to keep nodeSpacing: 50, //
comboSpacing: 150, // combo
gravity: 40,
comboGravity: 30,
}, },
modes: { modes: {
default: ['drag-canvas', 'zoom-canvas'], // Disable node and combo dragging default: ['drag-canvas', 'zoom-canvas', 'drag-combo', 'drag-node'],
}, },
defaultNode: { defaultNode: {
size: 50, size: nodeSize,
style: { style: {
lineWidth: 2, lineWidth: 2,
stroke: '#37ACD7', stroke: '#37ACD7',
@ -128,15 +121,15 @@ onMounted(() => {
}, },
defaultEdge: { defaultEdge: {
style: { style: {
stroke: '#37ACD7', stroke: '#FFFFFF',
lineWidth: 1, lineWidth: 2,
opacity: 0.5, opacity: 0.3,
}, },
}, },
defaultCombo: { // This is now a base style, individual styles will override defaultCombo: {
type: 'circle', type: 'circle',
style: { style: {
lineWidth: 1, lineWidth: 2,
}, },
}, },
}); });
@ -168,7 +161,7 @@ onMounted(() => {
.container { .container {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #0b1120; // Match the dark background from the screenshot background: #0b1120; // Set a background similar to the image
} }
} }
</style> </style>

View File

@ -10,5 +10,10 @@ export default defineConfig({
"@": path.resolve(__dirname, "src"), "@": path.resolve(__dirname, "src"),
"@assets": path.resolve(__dirname, "src/assets") "@assets": path.resolve(__dirname, "src/assets")
} }
} },
server: {
watch: {
usePolling: true, // 解决部分环境下热更新失效
}
},
}); });