树状知识图谱
This commit is contained in:
parent
0a6fff3cf2
commit
c58a029c10
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist.zip
|
||||||
|
build
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
10
src/apis/knowledge/chart.ts
Normal file
10
src/apis/knowledge/chart.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { request } from '@/utils/request/index.ts';
|
||||||
|
|
||||||
|
// 列表接口
|
||||||
|
export const dataApi = async (data?: Record<string, any>) => {
|
||||||
|
return await request({
|
||||||
|
url: `/v1.2/graph-connections/1/gremlin-query`,
|
||||||
|
method: 'POST',
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
};
|
||||||
@ -41,14 +41,16 @@ export const useChartHooks = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
animation: false, // 完全关闭动画
|
animation: false, // 完全关闭动画
|
||||||
animationDuration: 0, // 动画时长设为0
|
animationDuration: 10, // 动画时长设为0
|
||||||
animationDurationUpdate: 0, // 更新动画时长设为0
|
animationDurationUpdate: 10, // 更新动画时长设为0
|
||||||
animationEasingUpdate: "linear", // 使用线性缓动
|
animationEasingUpdate: "linear", // 使用线性缓动
|
||||||
force: {
|
force: {
|
||||||
repulsion: 1000,
|
|
||||||
|
repulsion: 800, // 减小排斥力,避免节点分布过散
|
||||||
gravity: 0.1, // 添加重力防止节点飞散
|
gravity: 0.1, // 添加重力防止节点飞散
|
||||||
friction: 0.6, // 添加摩擦力
|
friction: 1, // 添加摩擦力
|
||||||
layoutAnimation: false, // 关闭力导向布局动画
|
layoutAnimation: false, // 关闭力导向布局动画
|
||||||
|
edgeLength: 100, // 设置边的理想长度,影响关系文本位置
|
||||||
},
|
},
|
||||||
// animationDurationUpdate: 100,
|
// animationDurationUpdate: 100,
|
||||||
// animationEasingUpdate: "quinticInOut",
|
// animationEasingUpdate: "quinticInOut",
|
||||||
@ -56,7 +58,7 @@ export const useChartHooks = () => {
|
|||||||
normal: {
|
normal: {
|
||||||
show: true,
|
show: true,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 12,
|
fontSize: 10, // 减小文本大小,使其与节点大小更匹配
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -69,7 +71,7 @@ export const useChartHooks = () => {
|
|||||||
{
|
{
|
||||||
type: "graph",
|
type: "graph",
|
||||||
layout: "force",
|
layout: "force",
|
||||||
symbolSize: 60,
|
symbolSize: 30, // 增大节点大小,使其与文本大小比例更协调
|
||||||
focusNodeAdjacency: true,
|
focusNodeAdjacency: true,
|
||||||
roam: true,
|
roam: true,
|
||||||
categories: [
|
categories: [
|
||||||
@ -102,7 +104,7 @@ export const useChartHooks = () => {
|
|||||||
normal: {
|
normal: {
|
||||||
show: true,
|
show: true,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 12,
|
fontSize: 10, // 减小文本大小,使其与节点大小更匹配
|
||||||
},
|
},
|
||||||
formatter: (e: Record<string, any>) => {
|
formatter: (e: Record<string, any>) => {
|
||||||
const name = e.data.raw.properties.name
|
const name = e.data.raw.properties.name
|
||||||
@ -111,16 +113,19 @@ export const useChartHooks = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
force: {
|
force: {
|
||||||
repulsion: 1000,
|
repulsion: 800, // 减小排斥力,避免节点分布过散
|
||||||
|
edgeLength: 100, // 设置边的理想长度
|
||||||
},
|
},
|
||||||
edgeSymbolSize: [4, 50],
|
edgeSymbolSize: [4, 30], // 减小边的箭头大小,使其更协调
|
||||||
edgeLabel: {
|
edgeLabel: {
|
||||||
normal: {
|
normal: {
|
||||||
show: true,
|
show: true,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
fontSize: 10,
|
fontSize: 9, // 减小关系文本大小
|
||||||
},
|
},
|
||||||
formatter: "{c}",
|
formatter: "{c}",
|
||||||
|
position: 'middle', // 确保文本位于边的中间
|
||||||
|
offset: [0, 5], // 微调文本位置,避免与边重叠
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: [
|
data: [
|
||||||
@ -309,8 +314,10 @@ export const useChartHooks = () => {
|
|||||||
raw: data,
|
raw: data,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
draggable: options?.draggable || false,
|
draggable: false,
|
||||||
itemStyle: data?.itemStyle || null
|
itemStyle: data?.itemStyle || null,
|
||||||
|
x: data?.x|| null,
|
||||||
|
y: data?.y || null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
273
src/views/knowledge/hooks/location copy.ts
Normal file
273
src/views/knowledge/hooks/location copy.ts
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置管理钩子函数
|
||||||
|
* 用于生成和管理知识图谱节点的树状布局位置
|
||||||
|
*/
|
||||||
|
export const useLocationHooks = () => {
|
||||||
|
// 位置映射表,用于存储节点ID到位置的映射
|
||||||
|
const locationMap = ref(new Map<string, { x: number; y: number }>());
|
||||||
|
|
||||||
|
// 配置参数
|
||||||
|
const config = ref({
|
||||||
|
// 层级之间的垂直间距
|
||||||
|
levelGap: 200, // 纵向间距20
|
||||||
|
// 同一层级内节点的水平间距
|
||||||
|
nodeGap: 100, // 横向间距10
|
||||||
|
// 画布中心位置
|
||||||
|
centerX: 400,
|
||||||
|
centerY: 300,
|
||||||
|
// 初始缩放比例
|
||||||
|
scale: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算树状布局的节点位置
|
||||||
|
* @param nodes 节点列表
|
||||||
|
* @param relationships 关系列表
|
||||||
|
* @param rootIds 根节点ID列表
|
||||||
|
* @returns 更新后的节点列表(包含位置信息)
|
||||||
|
*/
|
||||||
|
const calculateTreeLayout = (nodes: any[], relationships: any[], rootIds: string[]) => {
|
||||||
|
// 清除之前的位置信息
|
||||||
|
clearLocations();
|
||||||
|
|
||||||
|
// 构建节点ID到节点的映射
|
||||||
|
const nodeMap = new Map(nodes.map(node => [node.id, node]));
|
||||||
|
|
||||||
|
// 构建父-子关系树
|
||||||
|
const tree = buildTreeStructure(nodeMap, relationships, rootIds);
|
||||||
|
|
||||||
|
console.log('tree', tree)
|
||||||
|
|
||||||
|
// 计算每个子树的边界
|
||||||
|
rootIds.forEach(rootId => {
|
||||||
|
calculateSubtreeBounds(tree[rootId]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算每个根树的位置偏移
|
||||||
|
let totalWidth = 0;
|
||||||
|
const rootOffsets: number[] = [];
|
||||||
|
|
||||||
|
// 计算总宽度
|
||||||
|
rootIds.forEach(rootId => {
|
||||||
|
const root = tree[rootId];
|
||||||
|
rootOffsets.push(totalWidth);
|
||||||
|
totalWidth += root.width * config.value.nodeGap;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 应用位置计算(从下往上)
|
||||||
|
rootIds.forEach((rootId, index) => {
|
||||||
|
// 计算根节点的水平位置(居中分布)
|
||||||
|
const offsetX = config.value.centerX - totalWidth * config.value.nodeGap * 0.5 + rootOffsets[index] * config.value.nodeGap;
|
||||||
|
|
||||||
|
// 递归计算每个节点的最终位置
|
||||||
|
calculateFinalPosition(tree[rootId], offsetX, config.value.centerY);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新节点列表,添加位置信息
|
||||||
|
return nodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
x: locationMap.value.get(node.id)?.x,
|
||||||
|
y: locationMap.value.get(node.id)?.y
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建树状数据结构
|
||||||
|
* @param nodeMap 节点映射表
|
||||||
|
* @param relationships 关系列表
|
||||||
|
* @param rootIds 根节点ID列表
|
||||||
|
* @returns 树状结构
|
||||||
|
*/
|
||||||
|
const buildTreeStructure = (nodeMap: Map<string, any>, relationships: any[], rootIds: string[]) => {
|
||||||
|
const tree: Record<string, any> = {};
|
||||||
|
const childrenMap = new Map<string, string[]>();
|
||||||
|
const parentMap = new Map<string, string>();
|
||||||
|
|
||||||
|
// 初始化每个节点的子节点列表
|
||||||
|
nodeMap.forEach((node, id) => {
|
||||||
|
tree[id] = {
|
||||||
|
id,
|
||||||
|
children: [],
|
||||||
|
// 用于布局计算的属性
|
||||||
|
width: 0, // 子树宽度(以节点数为单位)
|
||||||
|
left: 0, // 子树左边界(以节点数为单位)
|
||||||
|
right: 0, // 子树右边界(以节点数为单位)
|
||||||
|
center: 0 // 子树中心位置(以节点数为单位)
|
||||||
|
};
|
||||||
|
childrenMap.set(id, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构建父-子关系
|
||||||
|
relationships.forEach(relation => {
|
||||||
|
const parentId = relation.start_node_id;
|
||||||
|
const childId = relation.end_node_id;
|
||||||
|
if (childrenMap.has(parentId)) {
|
||||||
|
childrenMap.get(parentId)?.push(childId);
|
||||||
|
}
|
||||||
|
parentMap.set(childId, parentId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构建完整的树结构
|
||||||
|
childrenMap.forEach((childrenIds, parentId) => {
|
||||||
|
if (tree[parentId]) {
|
||||||
|
tree[parentId].children = childrenIds.map(childId => tree[childId]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算子树的边界和宽度(后序遍历)
|
||||||
|
* @param node 当前节点
|
||||||
|
*/
|
||||||
|
const calculateSubtreeBounds = (node: any) => {
|
||||||
|
// 如果是叶子节点
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
node.width = 1;
|
||||||
|
node.left = 0;
|
||||||
|
node.right = 0;
|
||||||
|
node.center = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归计算所有子节点的边界
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
calculateSubtreeBounds(child);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算当前节点的子树边界
|
||||||
|
let totalLeft = 0;
|
||||||
|
let totalRight = 0;
|
||||||
|
let totalWidth = 0;
|
||||||
|
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
// 更新总宽度
|
||||||
|
totalWidth += child.width;
|
||||||
|
if (node.children.length > 1) {
|
||||||
|
totalWidth += 1; // 添加子节点之间的间距(以节点数为单位)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算每个子节点的相对位置
|
||||||
|
let currentPos = 0;
|
||||||
|
node.children.forEach((child: any, index: number) => {
|
||||||
|
// 记录子节点的相对位置
|
||||||
|
child.relativeX = currentPos - child.center;
|
||||||
|
currentPos += child.width;
|
||||||
|
|
||||||
|
// 添加子节点之间的间距
|
||||||
|
if (index < node.children.length - 1) {
|
||||||
|
currentPos += 1; // 添加一个节点宽度的间距
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置当前节点的边界信息
|
||||||
|
node.width = totalWidth;
|
||||||
|
node.left = 0;
|
||||||
|
node.right = totalWidth - 1;
|
||||||
|
node.center = Math.floor(totalWidth / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算节点的最终位置(前序遍历)
|
||||||
|
* @param node 当前节点
|
||||||
|
* @param offsetX 水平偏移量
|
||||||
|
* @param y Y坐标
|
||||||
|
* @param level 当前层级
|
||||||
|
*/
|
||||||
|
const calculateFinalPosition = (node: any, offsetX: number, y: number, level: number = 0) => {
|
||||||
|
// 计算当前节点的X坐标
|
||||||
|
const nodeX = offsetX + node.center * config.value.nodeGap;
|
||||||
|
|
||||||
|
// 计算当前节点的Y坐标(从上往下排列)
|
||||||
|
const nodeY = y + level * config.value.levelGap;
|
||||||
|
|
||||||
|
// 存储节点位置
|
||||||
|
locationMap.value.set(node.id, {
|
||||||
|
x: nodeX,
|
||||||
|
y: nodeY
|
||||||
|
});
|
||||||
|
|
||||||
|
// 递归计算子节点的位置
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
const childOffsetX = offsetX + child.relativeX * config.value.nodeGap;
|
||||||
|
calculateFinalPosition(child, childOffsetX, y, level + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据节点ID获取位置
|
||||||
|
* @param id 节点ID
|
||||||
|
* @returns 节点位置
|
||||||
|
*/
|
||||||
|
const getLocationById = (id: string) => {
|
||||||
|
return locationMap.value.get(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量设置节点位置
|
||||||
|
* @param positions 位置映射对象 {nodeId: {x, y}}
|
||||||
|
*/
|
||||||
|
const setLocations = (positions: Record<string, { x: number; y: number }>) => {
|
||||||
|
Object.entries(positions).forEach(([id, position]) => {
|
||||||
|
locationMap.value.set(id, position);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有位置信息
|
||||||
|
*/
|
||||||
|
const clearLocations = () => {
|
||||||
|
locationMap.value.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置参数
|
||||||
|
* @param newConfig 新的配置参数
|
||||||
|
*/
|
||||||
|
const updateConfig = (newConfig: Partial<typeof config.value>) => {
|
||||||
|
config.value = { ...config.value, ...newConfig };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用位置信息到ECharts选项配置
|
||||||
|
* @param option ECharts选项配置
|
||||||
|
* @returns 更新后的ECharts选项配置
|
||||||
|
*/
|
||||||
|
const applyLocationsToOption = (option: any) => {
|
||||||
|
if (!option.series || !option.series[0]) return option;
|
||||||
|
|
||||||
|
const updatedSeries = {
|
||||||
|
...option.series[0],
|
||||||
|
// 更新节点数据,添加位置信息
|
||||||
|
data: option.series[0].data.map((node: any) => ({
|
||||||
|
...node,
|
||||||
|
...locationMap.value.get(node.id)
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...option,
|
||||||
|
series: [updatedSeries]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
locationMap,
|
||||||
|
config,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
calculateTreeLayout,
|
||||||
|
getLocationById,
|
||||||
|
setLocations,
|
||||||
|
clearLocations,
|
||||||
|
updateConfig,
|
||||||
|
applyLocationsToOption
|
||||||
|
};
|
||||||
|
};
|
||||||
266
src/views/knowledge/hooks/location.ts
Normal file
266
src/views/knowledge/hooks/location.ts
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置管理钩子函数
|
||||||
|
* 用于生成和管理知识图谱节点的树状布局位置
|
||||||
|
*/
|
||||||
|
export const useLocationHooks = () => {
|
||||||
|
// 位置映射表,用于存储节点ID到位置的映射
|
||||||
|
const locationMap = ref(new Map<string, { x: number; y: number }>());
|
||||||
|
|
||||||
|
// 配置参数
|
||||||
|
const config = ref({
|
||||||
|
// 层级之间的垂直间距
|
||||||
|
levelGap: 120, // 增加垂直间距,避免节点垂直重叠
|
||||||
|
// 同一层级内节点的水平间距
|
||||||
|
nodeGap: 180, // 增加水平间距,避免节点水平重叠
|
||||||
|
// 树之间的间距(增加以避免不同树的叶节点重叠)
|
||||||
|
treeGap: 200,
|
||||||
|
// 画布中心位置
|
||||||
|
centerX: 400,
|
||||||
|
centerY: 300,
|
||||||
|
// 初始缩放比例
|
||||||
|
scale: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算树状布局的节点位置
|
||||||
|
* @param nodes 节点列表
|
||||||
|
* @param relationships 关系列表
|
||||||
|
* @param rootIds 根节点ID列表
|
||||||
|
* @returns 更新后的节点列表(包含位置信息)
|
||||||
|
*/
|
||||||
|
const calculateTreeLayout = (nodes: any[], relationships: any[], rootIds: string[]) => {
|
||||||
|
// 清除旧的位置信息
|
||||||
|
clearLocations();
|
||||||
|
|
||||||
|
// 构建节点ID到节点的映射
|
||||||
|
const nodeMap = new Map(nodes.map(node => [node.id, node]));
|
||||||
|
|
||||||
|
// 构建父-子关系树(倒序)
|
||||||
|
const tree = buildTreeStructure(nodeMap, relationships, rootIds);
|
||||||
|
|
||||||
|
// 后序遍历计算每个子树的边界和中心位置
|
||||||
|
rootIds.slice().reverse().forEach(rootId => {
|
||||||
|
calculateSubtreeBounds(tree[rootId]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算所有根树的总宽度(包括树间距)
|
||||||
|
const totalWidth = rootIds.reduce((sum, rootId) => {
|
||||||
|
return sum + tree[rootId].width + config.value.treeGap;
|
||||||
|
}, 0) - config.value.treeGap; // 减去最后一个树间距
|
||||||
|
|
||||||
|
// 根据总宽度计算起始X位置,使整个布局居中
|
||||||
|
let currentX = config.value.centerX - totalWidth / 2;
|
||||||
|
|
||||||
|
// 前序遍历计算每个节点的最终位置
|
||||||
|
rootIds.slice().reverse().forEach((rootId, index) => {
|
||||||
|
const rootNode = tree[rootId];
|
||||||
|
|
||||||
|
// 计算当前根树的起始位置
|
||||||
|
const rootX = currentX + rootNode.width / 2;
|
||||||
|
|
||||||
|
// 递归计算每个节点的最终位置
|
||||||
|
calculateFinalPosition(rootNode, rootX, config.value.centerY, 0);
|
||||||
|
|
||||||
|
// 更新当前X位置,为下一个根树留出足够空间
|
||||||
|
currentX += rootNode.width + config.value.treeGap;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新节点列表,添加位置信息
|
||||||
|
return nodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
x: locationMap.value.get(node.id)?.x,
|
||||||
|
y: locationMap.value.get(node.id)?.y
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建树状数据结构(倒序)
|
||||||
|
* @param nodeMap 节点映射表
|
||||||
|
* @param relationships 关系列表
|
||||||
|
* @param rootIds 根节点ID列表
|
||||||
|
* @returns 树状结构
|
||||||
|
*/
|
||||||
|
const buildTreeStructure = (nodeMap: Map<string, any>, relationships: any[], rootIds: string[]) => {
|
||||||
|
const tree: Record<string, any> = {};
|
||||||
|
const childrenMap = new Map<string, string[]>();
|
||||||
|
const parentMap = new Map<string, string>();
|
||||||
|
|
||||||
|
// 初始化每个节点的子节点列表
|
||||||
|
nodeMap.forEach((node, id) => {
|
||||||
|
tree[id] = {
|
||||||
|
id,
|
||||||
|
children: [],
|
||||||
|
// 布局相关属性
|
||||||
|
width: 0, // 子树宽度
|
||||||
|
left: 0, // 子树最左边界
|
||||||
|
right: 0, // 子树最右边界
|
||||||
|
center: 0, // 子树中心位置
|
||||||
|
relativeX: 0 // 相对于父节点的X偏移
|
||||||
|
};
|
||||||
|
childrenMap.set(id, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构建父-子关系
|
||||||
|
relationships.forEach(relation => {
|
||||||
|
const parentId = relation.start_node_id;
|
||||||
|
const childId = relation.end_node_id;
|
||||||
|
if (childrenMap.has(parentId)) {
|
||||||
|
childrenMap.get(parentId)?.push(childId);
|
||||||
|
parentMap.set(childId, parentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 构建完整的树结构(子节点倒序排列)
|
||||||
|
childrenMap.forEach((childrenIds, parentId) => {
|
||||||
|
if (tree[parentId]) {
|
||||||
|
// 倒序添加子节点,确保从右到左布局
|
||||||
|
tree[parentId].children = childrenIds.slice().reverse().map(childId => tree[childId]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tree;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后序遍历计算子树边界
|
||||||
|
* @param node 当前节点
|
||||||
|
*/
|
||||||
|
const calculateSubtreeBounds = (node: any) => {
|
||||||
|
if (!node || !node.children || node.children.length === 0) {
|
||||||
|
// 叶子节点
|
||||||
|
node.width = config.value.nodeGap;
|
||||||
|
node.left = -config.value.nodeGap / 2;
|
||||||
|
node.right = config.value.nodeGap / 2;
|
||||||
|
node.center = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先计算所有子节点的边界
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
calculateSubtreeBounds(child);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算当前节点的边界
|
||||||
|
let totalWidth = 0;
|
||||||
|
const childCount = node.children.length;
|
||||||
|
|
||||||
|
// 计算所有子树的总宽度(包括间距)
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
totalWidth += child.width + config.value.nodeGap;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 减去最后一个子树后面的间距
|
||||||
|
totalWidth -= config.value.nodeGap;
|
||||||
|
|
||||||
|
// 计算每个子节点的相对位置
|
||||||
|
let currentX = -totalWidth / 2;
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
child.relativeX = currentX + child.width / 2;
|
||||||
|
currentX += child.width + config.value.nodeGap;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新当前节点的边界信息
|
||||||
|
node.width = totalWidth;
|
||||||
|
node.left = -totalWidth / 2;
|
||||||
|
node.right = totalWidth / 2;
|
||||||
|
node.center = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前序遍历计算节点的最终位置
|
||||||
|
* @param node 当前节点
|
||||||
|
* @param x 父节点的X坐标
|
||||||
|
* @param y 父节点的Y坐标
|
||||||
|
* @param level 当前层级
|
||||||
|
*/
|
||||||
|
const calculateFinalPosition = (node: any, x: number, y: number, level: number) => {
|
||||||
|
if (!node) return;
|
||||||
|
|
||||||
|
// 计算当前节点的最终位置
|
||||||
|
const nodeX = x + node.relativeX;
|
||||||
|
const nodeY = y + (level * config.value.levelGap);
|
||||||
|
|
||||||
|
// 存储节点位置
|
||||||
|
locationMap.value.set(node.id, { x: nodeX, y: nodeY });
|
||||||
|
|
||||||
|
// 递归计算子节点的位置
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
node.children.forEach((child: any) => {
|
||||||
|
calculateFinalPosition(child, nodeX, nodeY, level + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据节点ID获取位置
|
||||||
|
* @param id 节点ID
|
||||||
|
* @returns 节点位置
|
||||||
|
*/
|
||||||
|
const getLocationById = (id: string) => {
|
||||||
|
return locationMap.value.get(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量设置节点位置
|
||||||
|
* @param positions 位置映射对象 {nodeId: {x, y}}
|
||||||
|
*/
|
||||||
|
const setLocations = (positions: Record<string, { x: number; y: number }>) => {
|
||||||
|
Object.entries(positions).forEach(([id, position]) => {
|
||||||
|
locationMap.value.set(id, position);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有位置信息
|
||||||
|
*/
|
||||||
|
const clearLocations = () => {
|
||||||
|
locationMap.value.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置参数
|
||||||
|
* @param newConfig 新的配置参数
|
||||||
|
*/
|
||||||
|
const updateConfig = (newConfig: Partial<typeof config.value>) => {
|
||||||
|
config.value = { ...config.value, ...newConfig };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用位置信息到ECharts选项配置
|
||||||
|
* @param option ECharts选项配置
|
||||||
|
* @returns 更新后的ECharts选项配置
|
||||||
|
*/
|
||||||
|
const applyLocationsToOption = (option: any) => {
|
||||||
|
if (!option.series || !option.series[0]) return option;
|
||||||
|
|
||||||
|
const updatedSeries = {
|
||||||
|
...option.series[0],
|
||||||
|
// 更新节点数据,添加位置信息
|
||||||
|
data: option.series[0].data.map((node: any) => ({
|
||||||
|
...node,
|
||||||
|
...locationMap.value.get(node.id)
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...option,
|
||||||
|
series: [updatedSeries]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
locationMap,
|
||||||
|
config,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
calculateTreeLayout,
|
||||||
|
getLocationById,
|
||||||
|
setLocations,
|
||||||
|
clearLocations,
|
||||||
|
updateConfig,
|
||||||
|
applyLocationsToOption
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,8 +1,12 @@
|
|||||||
export const useUtils = () => {
|
export const useUtils = (options: Record<string, any>) => {
|
||||||
|
// 预设的颜色
|
||||||
|
const presetColors = [...(options?.colors || [])]
|
||||||
|
console.log('presetColors', presetColors)
|
||||||
// 存储已生成的颜色,确保不重复
|
// 存储已生成的颜色,确保不重复
|
||||||
const usedColors = new Set<string>();
|
const usedColors = new Set<string>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 生成随机颜色
|
// 生成随机颜色
|
||||||
const generateColor = (): string => {
|
const generateColor = (): string => {
|
||||||
// 生成更亮的颜色,避免太暗
|
// 生成更亮的颜色,避免太暗
|
||||||
@ -19,7 +23,8 @@ export const useUtils = () => {
|
|||||||
|
|
||||||
// 尝试生成不重复的颜色,最多尝试100次
|
// 尝试生成不重复的颜色,最多尝试100次
|
||||||
do {
|
do {
|
||||||
color = generateColor()
|
// 有预设先用预设
|
||||||
|
color = presetColors.shift() || generateColor()
|
||||||
attempts++
|
attempts++
|
||||||
} while (usedColors.has(color) && attempts < 100)
|
} while (usedColors.has(color) && attempts < 100)
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, inject, defineComponent, nextTick, computed, onUnmounted, onMounted } from "vue";
|
import {
|
||||||
import Chat from "@/views/knowledge/components/Chat/chat.vue";
|
ref,
|
||||||
import { useServices } from "@/views/knowledge/services/index";
|
inject,
|
||||||
import dayjs from "dayjs";
|
defineComponent,
|
||||||
import { Input, Select, SelectOption } from "ant-design-vue";
|
nextTick,
|
||||||
import _ from "lodash";
|
computed,
|
||||||
|
onUnmounted,
|
||||||
|
onMounted,
|
||||||
|
} from 'vue'
|
||||||
|
import Chat from '@/views/knowledge/components/Chat/chat.vue'
|
||||||
|
import { useServices } from '@/views/knowledge/services/index'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { Input, Select, SelectOption } from 'ant-design-vue'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const services = useServices();
|
const services = useServices()
|
||||||
|
|
||||||
const echartRef = ref<any>(null);
|
const echartRef = ref<any>(null)
|
||||||
|
|
||||||
const currentNode = ref({});
|
const currentNode = ref({})
|
||||||
const showRelationData = ref([]);
|
const showRelationData = ref([])
|
||||||
|
|
||||||
const echartInit = (chart: any) => {
|
const echartInit = (chart: any) => {
|
||||||
echartRef.value = chart;
|
echartRef.value = chart
|
||||||
echartRef.value.on("click", function (params: any) {
|
|
||||||
console.log("params", params);
|
echartRef.value.on('click', function (params: any) {
|
||||||
const nodeId = params.data.id;
|
console.log('params', params)
|
||||||
services.db.api.onPointClick(nodeId);
|
const nodeId = params.data.id
|
||||||
|
services.db.api.onPointClick(nodeId)
|
||||||
// onPointClick(params.data.id);
|
// onPointClick(params.data.id);
|
||||||
// console.log("params", params);
|
// console.log("params", params);
|
||||||
// // console.log("params", params);
|
// // console.log("params", params);
|
||||||
@ -27,30 +36,30 @@ const echartInit = (chart: any) => {
|
|||||||
// currentNode.value = params.data.raw;
|
// currentNode.value = params.data.raw;
|
||||||
// showRelationData.value = nodes;
|
// showRelationData.value = nodes;
|
||||||
// console.log("nodes", showRelationData.value);
|
// console.log("nodes", showRelationData.value);
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const showData = computed(() => {
|
const showData = computed(() => {
|
||||||
// console.log('useNode', showOptions)
|
// console.log('useNode', showOptions)
|
||||||
// return {};
|
// return {};
|
||||||
const curServer = "db";
|
const curServer = 'db'
|
||||||
return services[curServer].state.showOptions.value;
|
return services[curServer].state.showOptions.value
|
||||||
// return {};
|
// return {};
|
||||||
});
|
})
|
||||||
|
|
||||||
const onChange = _.debounce((id: string) => {
|
const onChange = _.debounce((id: string) => {
|
||||||
console.log(id, "id");
|
console.log(id, 'id')
|
||||||
services.db.api.onSearch(id);
|
services.db.api.onSearch(id)
|
||||||
}, 1000);
|
}, 1000)
|
||||||
|
|
||||||
const prefix = "knowledge-chat-page";
|
const prefix = 'knowledge-chat-page'
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
services.db.api.getData();
|
services.db.api.getData()
|
||||||
services.db.api.getQuery();
|
services.db.api.getQuery()
|
||||||
});
|
})
|
||||||
|
|
||||||
onUnmounted(() => {});
|
onUnmounted(() => {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -103,19 +112,31 @@ onUnmounted(() => {});
|
|||||||
<div class="node-wrapper">
|
<div class="node-wrapper">
|
||||||
<div class="label">切片:</div>
|
<div class="label">切片:</div>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template #title>{{ currentNode.properties.origin_text }}</template>
|
<template #title>{{
|
||||||
<div class="node-desc">{{ currentNode.properties.origin_text }}</div>
|
currentNode.properties.origin_text
|
||||||
|
}}</template>
|
||||||
|
<div class="node-desc">
|
||||||
|
{{ currentNode.properties.origin_text }}
|
||||||
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-wrapper">
|
<div class="node-wrapper">
|
||||||
<div class="label">创建时间:</div>
|
<div class="label">创建时间:</div>
|
||||||
|
|
||||||
{{ dayjs(currentNode.properties.created_at).format("YYYY-MM-DD hh:mm:ss") }}
|
{{
|
||||||
|
dayjs(currentNode.properties.created_at).format(
|
||||||
|
'YYYY-MM-DD hh:mm:ss'
|
||||||
|
)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="node-wrapper">
|
<div class="node-wrapper">
|
||||||
<div class="label">更新时间:</div>
|
<div class="label">更新时间:</div>
|
||||||
|
|
||||||
{{ dayjs(currentNode.properties.last_updated).format("YYYY-MM-DD hh:mm:ss") }}
|
{{
|
||||||
|
dayjs(currentNode.properties.last_updated).format(
|
||||||
|
'YYYY-MM-DD hh:mm:ss'
|
||||||
|
)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 关联 -->
|
<!-- 关联 -->
|
||||||
@ -142,18 +163,28 @@ onUnmounted(() => {});
|
|||||||
<div class="label">切片:</div>
|
<div class="label">切片:</div>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template #title>{{ item.node.properties.origin_text }}</template>
|
<template #title>{{ item.node.properties.origin_text }}</template>
|
||||||
<div class="node-desc">{{ item.node.properties.origin_text }}</div>
|
<div class="node-desc">
|
||||||
|
{{ item.node.properties.origin_text }}
|
||||||
|
</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-wrapper">
|
<div class="node-wrapper">
|
||||||
<div class="label">创建时间:</div>
|
<div class="label">创建时间:</div>
|
||||||
|
|
||||||
{{ dayjs(item.node.properties.created_at).format("YYYY-MM-DD hh:mm:ss") }}
|
{{
|
||||||
|
dayjs(item.node.properties.created_at).format(
|
||||||
|
'YYYY-MM-DD hh:mm:ss'
|
||||||
|
)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="node-wrapper">
|
<div class="node-wrapper">
|
||||||
<div class="label">更新时间:</div>
|
<div class="label">更新时间:</div>
|
||||||
|
|
||||||
{{ dayjs(item.node.properties.last_updated).format("YYYY-MM-DD hh:mm:ss") }}
|
{{
|
||||||
|
dayjs(item.node.properties.last_updated).format(
|
||||||
|
'YYYY-MM-DD hh:mm:ss'
|
||||||
|
)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -179,7 +210,7 @@ onUnmounted(() => {});
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$prefix: "knowledge-chat-page";
|
$prefix: 'knowledge-chat-page';
|
||||||
|
|
||||||
.#{$prefix} {
|
.#{$prefix} {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@ -1,356 +0,0 @@
|
|||||||
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import { router } from "@/router/index.ts";
|
|
||||||
import { createData } from './mockData'
|
|
||||||
|
|
||||||
|
|
||||||
// 存储已生成的颜色,确保不重复
|
|
||||||
const usedColors = new Set<string>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成随机且不重复的颜色
|
|
||||||
* @returns 十六进制颜色值
|
|
||||||
*/
|
|
||||||
const generateRandomColor = (): string => {
|
|
||||||
// 生成随机颜色
|
|
||||||
const generateColor = (): string => {
|
|
||||||
// 生成更亮的颜色,避免太暗
|
|
||||||
const r = Math.floor(Math.random() * 155) + 100
|
|
||||||
const g = Math.floor(Math.random() * 155) + 100
|
|
||||||
const b = Math.floor(Math.random() * 155) + 100
|
|
||||||
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let color: string
|
|
||||||
let attempts = 0
|
|
||||||
|
|
||||||
// 尝试生成不重复的颜色,最多尝试100次
|
|
||||||
do {
|
|
||||||
color = generateColor()
|
|
||||||
attempts++
|
|
||||||
} while (usedColors.has(color) && attempts < 100)
|
|
||||||
|
|
||||||
// 如果尝试次数过多,清空已使用的颜色重新开始
|
|
||||||
if (attempts >= 100) {
|
|
||||||
usedColors.clear()
|
|
||||||
color = generateColor()
|
|
||||||
}
|
|
||||||
|
|
||||||
usedColors.add(color)
|
|
||||||
return color
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为关系类型生成颜色映射
|
|
||||||
*/
|
|
||||||
const relationColors = ref<Map<string, string>>(new Map())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取关系类型对应的颜色
|
|
||||||
*/
|
|
||||||
const getRelationColor = (type: string): string => {
|
|
||||||
if (!relationColors.value.has(type)) {
|
|
||||||
relationColors.value.set(type, generateRandomColor())
|
|
||||||
}
|
|
||||||
return relationColors.value.get(type) || '#000000'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为节点类型生成颜色映射
|
|
||||||
*/
|
|
||||||
const nodeColors = ref<Map<string, string>>(new Map())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取节点类型对应的颜色
|
|
||||||
*/
|
|
||||||
const getNodeColor = (type: string): string => {
|
|
||||||
if (!nodeColors.value.has(type)) {
|
|
||||||
nodeColors.value.set(type, generateRandomColor())
|
|
||||||
}
|
|
||||||
return nodeColors.value.get(type) || '#000000'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const useChartServices = () => {
|
|
||||||
|
|
||||||
const { option, createPointData, createLink} = useChartHooks()
|
|
||||||
|
|
||||||
// 图表原始数据
|
|
||||||
const chatData = ref({})
|
|
||||||
|
|
||||||
// 类型字典集合
|
|
||||||
const typesMap = ref<string[]>([])
|
|
||||||
|
|
||||||
// 节点集合
|
|
||||||
const nodes = ref<Record<string, any>[]>([])
|
|
||||||
|
|
||||||
// 节点字典集合
|
|
||||||
const nodesMap = computed(() => {
|
|
||||||
const dataMap = new Map()
|
|
||||||
nodes.value.forEach((node) => {
|
|
||||||
if (!dataMap.has(node.id)) {
|
|
||||||
dataMap.set(node.id, node)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return dataMap
|
|
||||||
})
|
|
||||||
|
|
||||||
// 关系字典集合
|
|
||||||
const relationMap = computed(() => {
|
|
||||||
const dataMap = new Map()
|
|
||||||
console.log('relationMap', relations.value)
|
|
||||||
relations.value.forEach((relation) => {
|
|
||||||
if (!dataMap.has(relation.id)) {
|
|
||||||
dataMap.set(relation.raw.id, relation)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return dataMap
|
|
||||||
})
|
|
||||||
|
|
||||||
// 关系节点集合
|
|
||||||
const relations = ref<Record<string, any>[]>([])
|
|
||||||
|
|
||||||
// 数据规整
|
|
||||||
const chartConfig = {
|
|
||||||
chatData,
|
|
||||||
relationMap: relationMap,
|
|
||||||
typesMap,
|
|
||||||
nodes,
|
|
||||||
relations
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选中的类型
|
|
||||||
const selectTypes = ref<string[]>([])
|
|
||||||
|
|
||||||
// 选中的关系
|
|
||||||
const selectRelations = ref<string[]>([])
|
|
||||||
|
|
||||||
// 切换选中类型
|
|
||||||
const toggleSelectType = (type: string) => {
|
|
||||||
const i = selectTypes.value.indexOf(type)
|
|
||||||
if (i > -1) {
|
|
||||||
selectTypes.value.splice(i, 1)
|
|
||||||
} else {
|
|
||||||
selectTypes.value.push(type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换选中关系
|
|
||||||
const toggleRelation = (relation: string) => {
|
|
||||||
const i = selectRelations.value.indexOf(relation)
|
|
||||||
if (i > -1) {
|
|
||||||
selectRelations.value.splice(i, 1)
|
|
||||||
} else {
|
|
||||||
selectRelations.value.push(relation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置数据
|
|
||||||
const resetData = () => {
|
|
||||||
chatData.value = {}
|
|
||||||
relations.value = []
|
|
||||||
typesMap.value = []
|
|
||||||
nodes.value = []
|
|
||||||
// 重置颜色映射,以便下次生成新的颜色
|
|
||||||
relationColors.value.clear()
|
|
||||||
nodeColors.value.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增类型,已存在则跳过
|
|
||||||
const addTypeMap = (data: Record<string, any>) => {
|
|
||||||
if (typesMap.value.includes(data.labels)) return
|
|
||||||
typesMap.value.push(data.labels)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理数据
|
|
||||||
const handleData = (data: Record<string, any>) => {
|
|
||||||
const { nodes: rawNodes, relationships } = data
|
|
||||||
rawNodes.forEach((node: Record<string, any>) => {
|
|
||||||
addTypeMap(node)
|
|
||||||
// 获取节点类型对应的颜色
|
|
||||||
const nodeColor = getNodeColor(node.labels)
|
|
||||||
const nodeInfo = createPointData({
|
|
||||||
...node,
|
|
||||||
type: node.labels,
|
|
||||||
name: node.id,
|
|
||||||
itemStyle: {
|
|
||||||
color: nodeColor
|
|
||||||
},
|
|
||||||
// name: node.properties.name
|
|
||||||
})
|
|
||||||
nodes.value.push(nodeInfo)
|
|
||||||
})
|
|
||||||
relationships.forEach((relation: Record<string, any>) => {
|
|
||||||
// addRelationMap(relation)
|
|
||||||
// 获取关系类型对应的颜色
|
|
||||||
const relationColor = getRelationColor(relation.type)
|
|
||||||
const link = createLink({
|
|
||||||
...relation,
|
|
||||||
parentIndex: getNodeIndex(relation.start_node_id),
|
|
||||||
index: getNodeIndex(relation.end_node_id),
|
|
||||||
type: relation.type,
|
|
||||||
lineStyle: {
|
|
||||||
color: relationColor,
|
|
||||||
width: 2 // 关系线条宽度
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
color: relationColor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
relations.value.push(link)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取节点索引
|
|
||||||
const getNodeIndex = (id: string, source?: Record<string, any>) => {
|
|
||||||
return (source || nodes.value).findIndex((item:Record<string, any>) => item.id === id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示用的数据
|
|
||||||
const showData = computed(() => {
|
|
||||||
// 生成带有颜色的分类配置
|
|
||||||
// const categoriesWithColor = relationsMap.value.map(item => ({
|
|
||||||
// name: item,
|
|
||||||
// itemStyle: {
|
|
||||||
// normal: {
|
|
||||||
// color: getRelationColor(item)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }))
|
|
||||||
|
|
||||||
// 为每个节点添加category索引,使其与对应关系类型关联
|
|
||||||
const nodesWithCategory = nodes.value.map(node => ({
|
|
||||||
...node,
|
|
||||||
// category: relationsMap.value.indexOf(node.type) !== -1 ?
|
|
||||||
// relationsMap.value.indexOf(node.type) : 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const useData = {
|
|
||||||
...option.value,
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
...option.value.series[0],
|
|
||||||
// categories: categoriesWithColor,
|
|
||||||
data: nodesWithCategory,
|
|
||||||
links: relations.value,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('useData', useData)
|
|
||||||
return useData
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const getDataPath = () => {
|
|
||||||
const { id } = router.currentRoute.value.query;
|
|
||||||
const jsonMap: Record<string, any> = {
|
|
||||||
'1': 'http://222.190.139.186:10001/public/demo/knowledge-chat/neo4j_export2.json',
|
|
||||||
'2': 'http://222.190.139.186:10001/public/demo/knowledge-chat/neo4j_export.json'
|
|
||||||
}
|
|
||||||
return jsonMap[id as string] || jsonMap[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMockData = () => {
|
|
||||||
const data = createData()
|
|
||||||
return data
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const getData = async () => {
|
|
||||||
// const path = getDataPath()
|
|
||||||
// const res = await fetch(path)
|
|
||||||
// const mockData = await res.json()
|
|
||||||
const mockData = getMockData()
|
|
||||||
console.log('mockData', mockData)
|
|
||||||
chatData.value = mockData
|
|
||||||
handleData(chatData.value)
|
|
||||||
return mockData
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取节点关系
|
|
||||||
const getNodeRelationById = (id: string) => {
|
|
||||||
console.log('relations', relationMap.value)
|
|
||||||
const relationNode = []
|
|
||||||
|
|
||||||
relations.value.forEach((item: Record<string, any>) => {
|
|
||||||
if (item.raw.end_node_id == id || item.raw.start_node_id == id) {
|
|
||||||
let targetNodeId = null
|
|
||||||
// 如果是起始点, 相关点是终点
|
|
||||||
if (item.raw.start_node_id === id) {
|
|
||||||
targetNodeId = item.raw.end_node_id
|
|
||||||
} else {
|
|
||||||
// 如果是终点, 相关点是起始点
|
|
||||||
targetNodeId = item.raw.start_node_id
|
|
||||||
}
|
|
||||||
const nodeInfo = getNodeById(targetNodeId)
|
|
||||||
if (!nodeInfo) return
|
|
||||||
const params = {
|
|
||||||
type: item.raw.type,
|
|
||||||
node: nodeInfo.raw,
|
|
||||||
relation: item.raw
|
|
||||||
}
|
|
||||||
relationNode.push(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是起始点
|
|
||||||
// 如果是终点
|
|
||||||
// if (item.start_node_id === id) {
|
|
||||||
// targets.push(item)
|
|
||||||
// } else if (item.end_node_id === id) {
|
|
||||||
// ends.push(item)
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
return relationNode
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取节点信息
|
|
||||||
const getNodeById = (id: string) => {
|
|
||||||
if (!id) return
|
|
||||||
return nodesMap.value.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const searchValue = ref('')
|
|
||||||
|
|
||||||
// 显示节点
|
|
||||||
const onShowNodeEven = () => {
|
|
||||||
|
|
||||||
}
|
|
||||||
// 隐藏节点
|
|
||||||
const onHideNodeEven = () => {}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
option,
|
|
||||||
showData,
|
|
||||||
chartConfig,
|
|
||||||
relationColors,
|
|
||||||
nodeColors,
|
|
||||||
searchValue
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = {
|
|
||||||
getData,
|
|
||||||
handleData,
|
|
||||||
// addRelationMap,
|
|
||||||
addTypeMap,
|
|
||||||
resetData,
|
|
||||||
toggleSelectType,
|
|
||||||
toggleRelation,
|
|
||||||
getNodeById,
|
|
||||||
getNodeRelationById,
|
|
||||||
onShowNodeEven,
|
|
||||||
onHideNodeEven
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
state,
|
|
||||||
api
|
|
||||||
}
|
|
||||||
}
|
|
||||||
390
src/views/knowledge/services/db copy.ts
Normal file
390
src/views/knowledge/services/db copy.ts
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { createData } from './mockData'
|
||||||
|
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
||||||
|
import { useLocationHooks } from '@/views/knowledge/hooks/location.ts'
|
||||||
|
import { router } from "@/router/index.ts"
|
||||||
|
import { useUtils } from '@/views/knowledge/hooks/utils.ts'
|
||||||
|
|
||||||
|
export const useOperateServices = () => {
|
||||||
|
|
||||||
|
const lightColors = ['#D4BDFB', '#9DD0FF', '#57DFB8', '#59E433', '#FFD1A6', '#E1DF62', '#F5B4B4', '#8FE4ED']
|
||||||
|
const colors = ['#997AE7', '#7096E2', '#1FAA86', '#1BBB2B', '#F99D47', '#E4CF67', '#DF8D8D', '#4FC0D2']
|
||||||
|
|
||||||
|
|
||||||
|
const { createColor } = useUtils({
|
||||||
|
colors: colors
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { option, createPointData, createLink} = useChartHooks()
|
||||||
|
const {
|
||||||
|
calculateTreeLayout,
|
||||||
|
applyLocationsToOption,
|
||||||
|
getLocationById,
|
||||||
|
setLocations,
|
||||||
|
clearLocations
|
||||||
|
} = useLocationHooks()
|
||||||
|
|
||||||
|
|
||||||
|
// 图表数据
|
||||||
|
const graphData = ref<Record<string, any>>({
|
||||||
|
roots: [],
|
||||||
|
nodes: [],
|
||||||
|
relationships: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 节点映射
|
||||||
|
const nodesMap = new Map()
|
||||||
|
// 关系映射
|
||||||
|
const relationsMap = new Map()
|
||||||
|
// 分类映射
|
||||||
|
const categoriesMap = new Map()
|
||||||
|
|
||||||
|
// 更新位置
|
||||||
|
const updateLocation = (echartRef: Record<string, any>) => {
|
||||||
|
console.log('echartRef', echartRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取数据
|
||||||
|
const getData = () => {
|
||||||
|
const data = createData()
|
||||||
|
const roots = getMainNodes(data)
|
||||||
|
handleData(data)
|
||||||
|
graphData.value = {
|
||||||
|
roots: roots,
|
||||||
|
nodes: Array.from(nodesMap.values()),
|
||||||
|
relationships: Array.from(relationsMap.values())
|
||||||
|
}
|
||||||
|
console.log('graphData', graphData.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据
|
||||||
|
const updateData = () => {
|
||||||
|
graphData.value = {
|
||||||
|
...graphData.value,
|
||||||
|
nodes: Array.from(nodesMap.values()),
|
||||||
|
relationships: Array.from(relationsMap.values())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理数据
|
||||||
|
const handleData = (data: Record<string, any>) => {
|
||||||
|
const { nodes, relationships } = data
|
||||||
|
createCategoriesMap(nodes)
|
||||||
|
createNodeMap(nodes)
|
||||||
|
createRelationMap(relationships)
|
||||||
|
relationships.forEach((item: Record<string, any>) => {
|
||||||
|
const start = item.start_node_id
|
||||||
|
const end = item.end_node_id
|
||||||
|
if (nodesMap.has(start)) {
|
||||||
|
let target = nodesMap.get(start)
|
||||||
|
if (!target.childrenLinks) target.childrenLinks = []
|
||||||
|
target.childrenLinks.push(relationsMap.get(item.id))
|
||||||
|
}
|
||||||
|
if (nodesMap.has(end)) {
|
||||||
|
let target = nodesMap.get(end)
|
||||||
|
if (!target.parentLinks) target.parentLinks = []
|
||||||
|
target.parentLinks.push(relationsMap.get(item.id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
nodes,
|
||||||
|
relationships
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可视的节点
|
||||||
|
const showNodes = computed(() => {
|
||||||
|
const { roots, nodes } = graphData.value
|
||||||
|
const useNodes = nodes.filter((item: Record<string, any>) => {
|
||||||
|
const isRoots = roots.find((rootNode: Record<string, any>) => rootNode.id === item.id)
|
||||||
|
if (isRoots) return true
|
||||||
|
else {
|
||||||
|
return item.show
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return useNodes
|
||||||
|
})
|
||||||
|
|
||||||
|
// 可视的关系
|
||||||
|
const showRelations = computed(() => {
|
||||||
|
const useNode = showNodes.value
|
||||||
|
const useRelations: Record<string, any>[] = []
|
||||||
|
useNode?.map((item: Record<string, any>) => {
|
||||||
|
if (item.parentLinks) {
|
||||||
|
useRelations.push(...item.parentLinks)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
return useRelations
|
||||||
|
})
|
||||||
|
|
||||||
|
// 可视的echart配置
|
||||||
|
const showOptions = computed(() => {
|
||||||
|
|
||||||
|
const categories = Array.from(categoriesMap, ([key, value]) => {
|
||||||
|
return {
|
||||||
|
name: key,
|
||||||
|
itemStyle: {
|
||||||
|
color: value.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getCategoriesIndex = (category: string) => {
|
||||||
|
const i = categories.findIndex((item: Record<string, any>) => {
|
||||||
|
return item.name == category
|
||||||
|
})
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log('categories', categories)
|
||||||
|
|
||||||
|
// 应用固定布局
|
||||||
|
const nodesWithPositions = calculateTreeLayout(
|
||||||
|
showNodes.value,
|
||||||
|
showRelations.value,
|
||||||
|
graphData.value.roots.map((root: any) => root.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
const useNodes: Record<string, any>[] = []
|
||||||
|
nodesWithPositions.forEach((node: Record<string, any>) => {
|
||||||
|
const color = categoriesMap.get(node.label)?.color
|
||||||
|
const nodeInfo = createPointData({
|
||||||
|
...node,
|
||||||
|
itemStyle: {
|
||||||
|
color: color || '#333'
|
||||||
|
},
|
||||||
|
type: node.labels,
|
||||||
|
name: node.id,
|
||||||
|
})
|
||||||
|
useNodes.push(nodeInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const useRelations: Record<string, any>[] = []
|
||||||
|
showRelations.value.forEach((relation: Record<string, any>) => {
|
||||||
|
const link = createLink({
|
||||||
|
...relation,
|
||||||
|
parentIndex: getNodeIndex(relation.start_node_id, useNodes),
|
||||||
|
index: getNodeIndex(relation.end_node_id, useNodes),
|
||||||
|
type: relation.type,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2 // 关系线条宽度
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
useRelations.push(link)
|
||||||
|
})
|
||||||
|
|
||||||
|
const useOptions = {
|
||||||
|
...option.value,
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
...option.value.series[0],
|
||||||
|
// 使用固定布局替代力导向布局
|
||||||
|
layout: null, // 取消力导向布局
|
||||||
|
roam: true, // 允许缩放和平移
|
||||||
|
data: useNodes,
|
||||||
|
links: useRelations
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用位置信息到选项配置
|
||||||
|
return applyLocationsToOption(useOptions)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取节点索引
|
||||||
|
const getNodeIndex = (id: string, source: Record<string, any> = showNodes.value) => {
|
||||||
|
return source.findIndex((item: Record<string, any>) => item.id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到主节点
|
||||||
|
const getMainNodes = (data: Record<string, any>) => {
|
||||||
|
const { nodes, relationships } = data
|
||||||
|
let roots = JSON.parse(JSON.stringify(nodes))
|
||||||
|
relationships.forEach((item: Record<string, any>) => {
|
||||||
|
const end = item.end_node_id
|
||||||
|
roots = roots.filter((item: Record<string, any>) => {
|
||||||
|
return item.id !== end
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return roots
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建nodemap
|
||||||
|
const createNodeMap = (arr: Record<string, any>[]) => {
|
||||||
|
nodesMap.clear()
|
||||||
|
arr.forEach((item: Record<string, any>) => {
|
||||||
|
const raw = {
|
||||||
|
...item,
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
if (nodesMap.has(item.id)) {
|
||||||
|
// nodesMap.get(item.id).push(raw)
|
||||||
|
} else {
|
||||||
|
nodesMap.set(item.id, raw)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建分类categoriesMap
|
||||||
|
const createCategoriesMap = (arr: Record<string, any>[]) => {
|
||||||
|
categoriesMap.clear()
|
||||||
|
arr.forEach((item: Record<string, any>) => {
|
||||||
|
if (item.label) {
|
||||||
|
if (!categoriesMap.has(item.label)) {
|
||||||
|
categoriesMap.set(item.label, {
|
||||||
|
color: createColor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建关系map
|
||||||
|
const createRelationMap = (arr: Record<string, any>[]) => {
|
||||||
|
relationsMap.clear()
|
||||||
|
arr.forEach((item: Record<string, any>) => {
|
||||||
|
if (relationsMap.has(item.id)) {
|
||||||
|
// relationsMap.get(item.id).push(item)
|
||||||
|
} else {
|
||||||
|
relationsMap.set(item.id, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 根据id检索到对应的主节点
|
||||||
|
const getMainNodeById = (id: string) => {
|
||||||
|
const node = nodesMap.get(id)
|
||||||
|
const roots = []
|
||||||
|
const links = [...node.parentLinks || []]
|
||||||
|
if (!links.length) return [node]
|
||||||
|
while(links.length) {
|
||||||
|
const link = links.shift()
|
||||||
|
const start = link.start_node_id
|
||||||
|
const preNode = nodesMap.get(start)
|
||||||
|
console.log('preNode', preNode, nodesMap)
|
||||||
|
if (preNode?.parentLinks?.length) {
|
||||||
|
links.push(...preNode.parentLinks)
|
||||||
|
} else {
|
||||||
|
roots.push(preNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roots
|
||||||
|
}
|
||||||
|
|
||||||
|
// echart关系图节点点击
|
||||||
|
const onPointClick = (id: string) => {
|
||||||
|
const { roots } = graphData.value
|
||||||
|
const isRoots = roots.find((rootNode: Record<string, any>) => rootNode.id === id)
|
||||||
|
if (isRoots) {
|
||||||
|
onToggleMainPoint(id)
|
||||||
|
} else {
|
||||||
|
onTogglePoint(id)
|
||||||
|
}
|
||||||
|
console.log('categoriesMap', categoriesMap)
|
||||||
|
updateData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击节点
|
||||||
|
const onTogglePoint = (id: string) => {
|
||||||
|
const node = nodesMap.get(id)
|
||||||
|
if (node) {
|
||||||
|
// node.show = !node.show
|
||||||
|
const childrenLinks = node?.childrenLinks || []
|
||||||
|
console.log('childrenLinks', childrenLinks)
|
||||||
|
childrenLinks.forEach((item: Record<string, any>) => {
|
||||||
|
const { end_node_id } = item
|
||||||
|
const cnode = nodesMap.get(end_node_id)
|
||||||
|
const type = !cnode.show
|
||||||
|
setNodeTypeInLink(end_node_id, type)
|
||||||
|
})
|
||||||
|
// node.show = !node.show
|
||||||
|
// setNodeTypeInLink(id, !node.show)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击主节点,将当前节点下的所有子节点都显示或者隐藏出来
|
||||||
|
const onToggleMainPoint = (id: string) => {
|
||||||
|
const node = nodesMap.get(id)
|
||||||
|
if (node) {
|
||||||
|
setNodeTypeInLink(id, !node.show)
|
||||||
|
}
|
||||||
|
// 将当前节点下的所有子节点都显示或者隐藏出来
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链式更新数据可视信息
|
||||||
|
const setNodeTypeInLink = (id: string, type: boolean) => {
|
||||||
|
const node = nodesMap.get(id)
|
||||||
|
if (node) {
|
||||||
|
// 如果布尔值一致,不处理
|
||||||
|
// if (node.show != type) {
|
||||||
|
node.show = type
|
||||||
|
// }
|
||||||
|
const childrenLinks = node?.childrenLinks || []
|
||||||
|
childrenLinks.forEach((item: Record<string, any>) => {
|
||||||
|
const { end_node_id } = item
|
||||||
|
setNodeTypeInLink(end_node_id, type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键字
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
// 选项过滤
|
||||||
|
const optionsFilter = (input: string, option: any) => {
|
||||||
|
const raw = option['data-raw']
|
||||||
|
const id = raw.id
|
||||||
|
const name = raw.properties.name
|
||||||
|
return id.includes(input) || name.includes(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索
|
||||||
|
const onSearch = (id: string) => {
|
||||||
|
const roots = getMainNodeById(id)
|
||||||
|
console.log('roots', roots)
|
||||||
|
roots.forEach(item => {
|
||||||
|
console.log('item', item)
|
||||||
|
// if (item.show)
|
||||||
|
setNodeTypeInLink(item.id, true)
|
||||||
|
})
|
||||||
|
updateData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQuery = () => {
|
||||||
|
const { id } = router.currentRoute.value.query;
|
||||||
|
if (id) {
|
||||||
|
onSearch(id as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
state: {
|
||||||
|
graphData,
|
||||||
|
nodesMap,
|
||||||
|
relationsMap,
|
||||||
|
showOptions,
|
||||||
|
searchValue,
|
||||||
|
},
|
||||||
|
api: {
|
||||||
|
getData,
|
||||||
|
onPointClick,
|
||||||
|
optionsFilter,
|
||||||
|
updateLocation,
|
||||||
|
// onMainSelect,
|
||||||
|
// onSelectPoint,
|
||||||
|
onSearch,
|
||||||
|
getQuery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,16 +1,32 @@
|
|||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { createData } from './mockData'
|
import { createData } from './mockData'
|
||||||
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
import { useChartHooks } from '@/views/knowledge/hooks/chart.ts'
|
||||||
import { router } from "@/router/index.ts";
|
import { useLocationHooks } from '@/views/knowledge/hooks/location.ts'
|
||||||
|
import { router } from "@/router/index.ts"
|
||||||
import { useUtils } from '@/views/knowledge/hooks/utils.ts'
|
import { useUtils } from '@/views/knowledge/hooks/utils.ts'
|
||||||
|
|
||||||
export const useOperateServices = () => {
|
export const useOperateServices = () => {
|
||||||
|
|
||||||
const { createColor } = useUtils()
|
const lightColors = ['#D4BDFB', '#9DD0FF', '#57DFB8', '#59E433', '#FFD1A6', '#E1DF62', '#F5B4B4', '#8FE4ED']
|
||||||
|
const colors = ['#997AE7', '#7096E2', '#1FAA86', '#1BBB2B', '#F99D47', '#E4CF67', '#DF8D8D', '#4FC0D2']
|
||||||
|
|
||||||
|
|
||||||
|
const { createColor } = useUtils({
|
||||||
|
colors: colors
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { option, createPointData, createLink} = useChartHooks()
|
const { option, createPointData, createLink} = useChartHooks()
|
||||||
|
const {
|
||||||
|
calculateTreeLayout,
|
||||||
|
applyLocationsToOption,
|
||||||
|
getLocationById,
|
||||||
|
setLocations,
|
||||||
|
clearLocations
|
||||||
|
} = useLocationHooks()
|
||||||
|
|
||||||
const colors = ['#5470C6', '#91CC75', '#EE6666', '#73C0DE', '#3BA272', '#FC8452', '#9A60B4', '#EA7CC3']
|
|
||||||
|
|
||||||
// 图表数据
|
// 图表数据
|
||||||
const graphData = ref<Record<string, any>>({
|
const graphData = ref<Record<string, any>>({
|
||||||
@ -26,6 +42,11 @@ export const useOperateServices = () => {
|
|||||||
// 分类映射
|
// 分类映射
|
||||||
const categoriesMap = new Map()
|
const categoriesMap = new Map()
|
||||||
|
|
||||||
|
// 更新位置
|
||||||
|
const updateLocation = (echartRef: Record<string, any>) => {
|
||||||
|
console.log('echartRef', echartRef)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
const data = createData()
|
const data = createData()
|
||||||
@ -112,31 +133,44 @@ export const useOperateServices = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const getCategoriesIndex = (category: string) => {
|
|
||||||
const i = categories.findIndex((item: Record<string, any>) => {
|
|
||||||
return item.name == category
|
|
||||||
})
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
console.log('categories', categories)
|
|
||||||
|
|
||||||
// const link = ()
|
|
||||||
|
// 应用固定布局
|
||||||
|
const nodesWithPositions = calculateTreeLayout(
|
||||||
|
showNodes.value,
|
||||||
|
showRelations.value,
|
||||||
|
graphData.value.roots.map((root: any) => root.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('nodesWithPositions', nodesWithPositions)
|
||||||
|
|
||||||
const useNodes: Record<string, any>[] = []
|
const useNodes: Record<string, any>[] = []
|
||||||
showNodes.value.forEach((node: Record<string, any>) => {
|
nodesWithPositions.forEach((node: Record<string, any>) => {
|
||||||
const color = categoriesMap.get(node.label)?.color
|
const color = categoriesMap.get(node.label)?.color
|
||||||
console.log('color', categoriesMap, color, node.label)
|
const nodeInfo = {
|
||||||
const nodeInfo = createPointData({
|
raw: node,
|
||||||
...node,
|
id: node.id,
|
||||||
|
name: node.id,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: color || '#333'
|
color: color || '#333'
|
||||||
},
|
},
|
||||||
|
type: node.label,
|
||||||
type: node.labels,
|
x: node.x,
|
||||||
name: node.id,
|
y: node.y
|
||||||
|
}
|
||||||
})
|
// const nodeInfo = createPointData({
|
||||||
|
// ...node,
|
||||||
|
// itemStyle: {
|
||||||
|
// color: color || '#333'
|
||||||
|
// },
|
||||||
|
// type: node.labels,
|
||||||
|
// name: node.id,
|
||||||
|
// // 直接在创建节点时设置位置信息
|
||||||
|
// x: node.x,
|
||||||
|
// y: node.y
|
||||||
|
// })
|
||||||
useNodes.push(nodeInfo)
|
useNodes.push(nodeInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -161,12 +195,19 @@ export const useOperateServices = () => {
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
...option.value.series[0],
|
...option.value.series[0],
|
||||||
// categories: categoriesWithColor,
|
// 使用固定布局替代力导向布局
|
||||||
|
layout: null, // 取消力导向布局
|
||||||
|
roam: true, // 允许缩放和平移
|
||||||
data: useNodes,
|
data: useNodes,
|
||||||
links: useRelations
|
links: useRelations
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('useOptions', useOptions)
|
||||||
|
|
||||||
|
|
||||||
|
// 位置信息已经在创建节点时设置,不需要再次应用
|
||||||
return useOptions
|
return useOptions
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -351,6 +392,7 @@ export const useOperateServices = () => {
|
|||||||
getData,
|
getData,
|
||||||
onPointClick,
|
onPointClick,
|
||||||
optionsFilter,
|
optionsFilter,
|
||||||
|
updateLocation,
|
||||||
// onMainSelect,
|
// onMainSelect,
|
||||||
// onSelectPoint,
|
// onSelectPoint,
|
||||||
onSearch,
|
onSearch,
|
||||||
|
|||||||
@ -1,21 +1,37 @@
|
|||||||
export const createData = () => {
|
export const createData = () => {
|
||||||
const size = 3 // 每层级节点个数
|
|
||||||
const level = 3 // 层级
|
|
||||||
|
|
||||||
|
|
||||||
const data: Record<string, any> = {
|
const data: Record<string, any> = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
relationships: []
|
relationships: []
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let i = 1; i < size; i++) {
|
// 创建根节点
|
||||||
data.nodes.push(createNode(`${i}`))
|
for (let i = 1; i <= 4; i++) {
|
||||||
for(let j = 0; j< level; j++) {
|
const rootId = `${i}`;
|
||||||
data.nodes.push(createNode(`${i}${j}`))
|
data.nodes.push(createNode(rootId));
|
||||||
data.relationships.push(createRelations(`${i}${j}`, size))
|
|
||||||
for(let l = 0; l < level; l++) {
|
// 为每个根节点创建不同深度和宽度的子树
|
||||||
data.nodes.push(createNode(`${i}${j}${l}`))
|
const childCount = Math.floor(Math.random() * 3) + 2; // 2-4个子节点
|
||||||
data.relationships.push(createRelations(`${i}${j}${l}`, size))
|
for (let j = 0; j < childCount; j++) {
|
||||||
|
const childId = `${i}${j}`;
|
||||||
|
data.nodes.push(createNode(childId));
|
||||||
|
data.relationships.push(createRelations(childId, rootId));
|
||||||
|
|
||||||
|
// 创建第三层节点(不同数量)
|
||||||
|
const grandchildCount = Math.floor(Math.random() * 4) + 2; // 2-5个孙节点
|
||||||
|
for (let k = 0; k < grandchildCount; k++) {
|
||||||
|
const grandchildId = `${i}${j}${k}`;
|
||||||
|
data.nodes.push(createNode(grandchildId));
|
||||||
|
data.relationships.push(createRelations(grandchildId, childId));
|
||||||
|
|
||||||
|
// 随机为一些孙节点创建第四层节点
|
||||||
|
if (Math.random() > 0.5) {
|
||||||
|
const greatGrandchildCount = Math.floor(Math.random() * 3) + 1; // 1-3个曾孙节点
|
||||||
|
for (let l = 0; l < greatGrandchildCount; l++) {
|
||||||
|
const greatGrandchildId = `${i}${j}${k}${l}`;
|
||||||
|
data.nodes.push(createNode(greatGrandchildId));
|
||||||
|
data.relationships.push(createRelations(greatGrandchildId, grandchildId));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,12 +49,9 @@ const createNode = (index: number | string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRelations = (index: string, max: number) => {
|
const createRelations = (end: string, start: string) => {
|
||||||
const len = index.length
|
|
||||||
const start = index.slice(0, len - 1)
|
|
||||||
const end = index
|
|
||||||
const params = {
|
const params = {
|
||||||
id: `r${index}`,
|
id: `r${end}`,
|
||||||
"type": "组成", // 关系类型
|
"type": "组成", // 关系类型
|
||||||
"start_node_id": start, // 起始节点id
|
"start_node_id": start, // 起始节点id
|
||||||
"end_node_id": end,
|
"end_node_id": end,
|
||||||
|
|||||||
@ -13,9 +13,8 @@ function pathResolve(dir: string) {
|
|||||||
return resolve(__dirname, dir)
|
return resolve(__dirname, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
const URL_USE = ''
|
const URL_USE = 'http://ip:8088'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: './',
|
base: './',
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user