|
|
@@ -6,21 +6,22 @@ import Hierarchy from '@antv/hierarchy'; |
|
|
export const nodeWidth = 90; |
|
|
export const nodeWidth = 90; |
|
|
export const nodeHeight = 40; |
|
|
export const nodeHeight = 40; |
|
|
export const vGap = nodeHeight + 20; |
|
|
export const vGap = nodeHeight + 20; |
|
|
export const hGap = nodeWidth; |
|
|
|
|
|
|
|
|
export const hGap = nodeHeight + 20; |
|
|
export const ellipseWidth = nodeWidth; |
|
|
export const ellipseWidth = nodeWidth; |
|
|
export const labelPadding = 30; |
|
|
export const labelPadding = 30; |
|
|
export const nodeFontSize = 8; |
|
|
export const nodeFontSize = 8; |
|
|
|
|
|
export const datasetHGap = 20; |
|
|
|
|
|
|
|
|
// 数据集节点 |
|
|
// 数据集节点 |
|
|
const datasetNodes: NodeConfig[] = []; |
|
|
const datasetNodes: NodeConfig[] = []; |
|
|
|
|
|
|
|
|
export enum NodeType { |
|
|
export enum NodeType { |
|
|
current = 'current', |
|
|
|
|
|
parent = 'parent', |
|
|
|
|
|
children = 'children', |
|
|
|
|
|
project = 'project', |
|
|
|
|
|
trainDataset = 'trainDataset', |
|
|
|
|
|
testDataset = 'testDataset', |
|
|
|
|
|
|
|
|
Current = 'Current', // 当前模型 |
|
|
|
|
|
Parent = 'Parent', // 父模型 |
|
|
|
|
|
Children = 'Children', // 子模型 |
|
|
|
|
|
Project = 'Project', // 项目 |
|
|
|
|
|
TrainDataset = 'TrainDataset', // 训练数据集 |
|
|
|
|
|
TestDataset = 'TestDataset', // 测试数据集 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export type Rect = { |
|
|
export type Rect = { |
|
|
@@ -40,14 +41,14 @@ export interface TrainDataset extends NodeConfig { |
|
|
dataset_id: number; |
|
|
dataset_id: number; |
|
|
dataset_name: string; |
|
|
dataset_name: string; |
|
|
dataset_version: string; |
|
|
dataset_version: string; |
|
|
model_type: NodeType.testDataset | NodeType.trainDataset; |
|
|
|
|
|
|
|
|
model_type: NodeType.TestDataset | NodeType.TrainDataset; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export interface ProjectDependency extends NodeConfig { |
|
|
export interface ProjectDependency extends NodeConfig { |
|
|
url: string; |
|
|
url: string; |
|
|
name: string; |
|
|
name: string; |
|
|
branch: string; |
|
|
branch: string; |
|
|
model_type: NodeType.project; |
|
|
|
|
|
|
|
|
model_type: NodeType.Project; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export type ModalDetail = { |
|
|
export type ModalDetail = { |
|
|
@@ -66,9 +67,9 @@ export interface ModelDepsAPIData { |
|
|
version: string; |
|
|
version: string; |
|
|
workflow_id: number; |
|
|
workflow_id: number; |
|
|
exp_ins_id: number; |
|
|
exp_ins_id: number; |
|
|
model_type: NodeType.children | NodeType.current | NodeType.parent; |
|
|
|
|
|
|
|
|
model_type: NodeType.Children | NodeType.Current | NodeType.Parent; |
|
|
current_model_name: string; |
|
|
current_model_name: string; |
|
|
project_dependency: ProjectDependency; |
|
|
|
|
|
|
|
|
project_dependency?: ProjectDependency; |
|
|
test_dataset: TrainDataset[]; |
|
|
test_dataset: TrainDataset[]; |
|
|
train_dataset: TrainDataset[]; |
|
|
train_dataset: TrainDataset[]; |
|
|
train_task: TrainTask; |
|
|
train_task: TrainTask; |
|
|
@@ -79,16 +80,22 @@ export interface ModelDepsAPIData { |
|
|
|
|
|
|
|
|
export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { |
|
|
export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { |
|
|
children: ModelDepsData[]; |
|
|
children: ModelDepsData[]; |
|
|
|
|
|
expanded: boolean; // 是否展开 |
|
|
|
|
|
level: number; // 层级,从 0 开始 |
|
|
|
|
|
datasetLen: number; // 数据集数量 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 规范化子数据 |
|
|
// 规范化子数据 |
|
|
export function normalizeChildren(data: ModelDepsData[]) { |
|
|
export function normalizeChildren(data: ModelDepsData[]) { |
|
|
if (Array.isArray(data)) { |
|
|
if (Array.isArray(data)) { |
|
|
data.forEach((item) => { |
|
|
data.forEach((item) => { |
|
|
item.model_type = NodeType.children; |
|
|
|
|
|
|
|
|
item.model_type = NodeType.Children; |
|
|
|
|
|
item.expanded = false; |
|
|
|
|
|
item.level = 0; |
|
|
|
|
|
item.datasetLen = item.train_dataset.length + item.test_dataset.length; |
|
|
item.id = `$M_${item.current_model_id}_${item.version}`; |
|
|
item.id = `$M_${item.current_model_id}_${item.version}`; |
|
|
item.label = getLabel(item); |
|
|
item.label = getLabel(item); |
|
|
item.style = getStyle(NodeType.children); |
|
|
|
|
|
|
|
|
item.style = getStyle(NodeType.Children); |
|
|
normalizeChildren(item.children); |
|
|
normalizeChildren(item.children); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
@@ -111,22 +118,22 @@ export function getLabel(node: ModelDepsData | ModelDepsAPIData) { |
|
|
export function getStyle(model_type: NodeType) { |
|
|
export function getStyle(model_type: NodeType) { |
|
|
let fill = ''; |
|
|
let fill = ''; |
|
|
switch (model_type) { |
|
|
switch (model_type) { |
|
|
case NodeType.current: |
|
|
|
|
|
|
|
|
case NodeType.Current: |
|
|
fill = 'l(0) 0:#72a1ff 1:#1664ff'; |
|
|
fill = 'l(0) 0:#72a1ff 1:#1664ff'; |
|
|
break; |
|
|
break; |
|
|
case NodeType.parent: |
|
|
|
|
|
|
|
|
case NodeType.Parent: |
|
|
fill = 'l(0) 0:#93dfd1 1:#43c9b1'; |
|
|
fill = 'l(0) 0:#93dfd1 1:#43c9b1'; |
|
|
break; |
|
|
break; |
|
|
case NodeType.children: |
|
|
|
|
|
|
|
|
case NodeType.Children: |
|
|
fill = 'l(0) 0:#72b4ff 1:#169aff'; |
|
|
fill = 'l(0) 0:#72b4ff 1:#169aff'; |
|
|
break; |
|
|
break; |
|
|
case NodeType.project: |
|
|
|
|
|
|
|
|
case NodeType.Project: |
|
|
fill = 'l(0) 0:#b3a9ff 1:#8981ff'; |
|
|
fill = 'l(0) 0:#b3a9ff 1:#8981ff'; |
|
|
break; |
|
|
break; |
|
|
case NodeType.trainDataset: |
|
|
|
|
|
|
|
|
case NodeType.TrainDataset: |
|
|
fill = '#a5d878'; |
|
|
fill = '#a5d878'; |
|
|
break; |
|
|
break; |
|
|
case NodeType.testDataset: |
|
|
|
|
|
|
|
|
case NodeType.TestDataset: |
|
|
fill = '#d8b578'; |
|
|
fill = '#d8b578'; |
|
|
break; |
|
|
break; |
|
|
default: |
|
|
default: |
|
|
@@ -145,11 +152,15 @@ export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { |
|
|
}) as ModelDepsData; |
|
|
}) as ModelDepsData; |
|
|
|
|
|
|
|
|
// 设置当前模型的数据 |
|
|
// 设置当前模型的数据 |
|
|
normalizedData.model_type = NodeType.current; |
|
|
|
|
|
|
|
|
normalizedData.model_type = NodeType.Current; |
|
|
normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; |
|
|
normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; |
|
|
normalizedData.label = getLabel(normalizedData); |
|
|
normalizedData.label = getLabel(normalizedData); |
|
|
normalizedData.style = getStyle(NodeType.current); |
|
|
|
|
|
|
|
|
normalizedData.style = getStyle(NodeType.Current); |
|
|
|
|
|
normalizedData.expanded = true; |
|
|
|
|
|
normalizedData.datasetLen = |
|
|
|
|
|
normalizedData.train_dataset.length + normalizedData.test_dataset.length; |
|
|
normalizeChildren(normalizedData.children as ModelDepsData[]); |
|
|
normalizeChildren(normalizedData.children as ModelDepsData[]); |
|
|
|
|
|
normalizedData.level = 0; |
|
|
|
|
|
|
|
|
// 将 parent_models 转换成树形结构 |
|
|
// 将 parent_models 转换成树形结构 |
|
|
let parent_models = normalizedData.parent_models || []; |
|
|
let parent_models = normalizedData.parent_models || []; |
|
|
@@ -157,10 +168,13 @@ export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { |
|
|
const parent = parent_models[0]; |
|
|
const parent = parent_models[0]; |
|
|
normalizedData = { |
|
|
normalizedData = { |
|
|
...parent, |
|
|
...parent, |
|
|
model_type: NodeType.parent, |
|
|
|
|
|
|
|
|
expanded: false, |
|
|
|
|
|
level: 0, |
|
|
|
|
|
datasetLen: parent.train_dataset.length + parent.test_dataset.length, |
|
|
|
|
|
model_type: NodeType.Parent, |
|
|
id: `$M_${parent.current_model_id}_${parent.version}`, |
|
|
id: `$M_${parent.current_model_id}_${parent.version}`, |
|
|
label: getLabel(parent), |
|
|
label: getLabel(parent), |
|
|
style: getStyle(NodeType.parent), |
|
|
|
|
|
|
|
|
style: getStyle(NodeType.Parent), |
|
|
children: [ |
|
|
children: [ |
|
|
{ |
|
|
{ |
|
|
...normalizedData, |
|
|
...normalizedData, |
|
|
@@ -174,13 +188,34 @@ export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 |
|
|
// 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 |
|
|
export function getGraphData(data: ModelDepsData): GraphData { |
|
|
|
|
|
|
|
|
export function getGraphData(data: ModelDepsData, hierarchyNodes: ModelDepsData[]): GraphData { |
|
|
const config = { |
|
|
const config = { |
|
|
direction: 'LR', |
|
|
direction: 'LR', |
|
|
getHeight: () => nodeHeight, |
|
|
getHeight: () => nodeHeight, |
|
|
getWidth: () => nodeWidth, |
|
|
getWidth: () => nodeWidth, |
|
|
getVGap: () => vGap / 2, |
|
|
|
|
|
getHGap: () => hGap / 2, |
|
|
|
|
|
|
|
|
getVGap: (node: NodeConfig) => { |
|
|
|
|
|
const model = node as ModelDepsData; |
|
|
|
|
|
const { model_type, expanded, project_dependency } = model; |
|
|
|
|
|
if (model_type === NodeType.Current || model_type === NodeType.Parent) { |
|
|
|
|
|
return vGap / 2; |
|
|
|
|
|
} |
|
|
|
|
|
const selfGap = expanded && project_dependency?.url ? nodeHeight + vGap : 0; |
|
|
|
|
|
const nextNode = getSameHierarchyNextNode(model, hierarchyNodes); |
|
|
|
|
|
if (!nextNode) { |
|
|
|
|
|
return vGap / 2; |
|
|
|
|
|
} |
|
|
|
|
|
const nextGap = nextNode.expanded === true && nextNode.datasetLen > 0 ? nodeHeight + vGap : 0; |
|
|
|
|
|
return (selfGap + nextGap + vGap) / 2; |
|
|
|
|
|
}, |
|
|
|
|
|
getHGap: (node: NodeConfig) => { |
|
|
|
|
|
const model = node as ModelDepsData; |
|
|
|
|
|
return ( |
|
|
|
|
|
(getHierarchyWidth(model.level, hierarchyNodes) + |
|
|
|
|
|
getHierarchyWidth(model.level + 1, hierarchyNodes) + |
|
|
|
|
|
hGap) / |
|
|
|
|
|
2 |
|
|
|
|
|
); |
|
|
|
|
|
}, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// 树形布局计算出坐标 |
|
|
// 树形布局计算出坐标 |
|
|
@@ -191,11 +226,11 @@ export function getGraphData(data: ModelDepsData): GraphData { |
|
|
Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { |
|
|
Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { |
|
|
const data = node.data as ModelDepsData; |
|
|
const data = node.data as ModelDepsData; |
|
|
// 当前模型显示数据集和项目 |
|
|
// 当前模型显示数据集和项目 |
|
|
if (data.model_type === NodeType.current) { |
|
|
|
|
|
|
|
|
if (data.expanded === true) { |
|
|
addDatasetDependency(data, node, nodes, edges); |
|
|
addDatasetDependency(data, node, nodes, edges); |
|
|
addProjectDependency(data, node, nodes, edges); |
|
|
addProjectDependency(data, node, nodes, edges); |
|
|
} else if (data.model_type === NodeType.children) { |
|
|
|
|
|
adjustDatasetPosition(node); |
|
|
|
|
|
|
|
|
} else if (data.model_type === NodeType.Children) { |
|
|
|
|
|
// adjustDatasetPosition(node); |
|
|
} |
|
|
} |
|
|
nodes.push({ |
|
|
nodes.push({ |
|
|
...data, |
|
|
...data, |
|
|
@@ -219,16 +254,16 @@ const addDatasetDependency = ( |
|
|
nodes: NodeConfig[], |
|
|
nodes: NodeConfig[], |
|
|
edges: EdgeConfig[], |
|
|
edges: EdgeConfig[], |
|
|
) => { |
|
|
) => { |
|
|
const { train_dataset, test_dataset } = data; |
|
|
|
|
|
|
|
|
const { train_dataset, test_dataset, id } = data; |
|
|
train_dataset.forEach((item) => { |
|
|
train_dataset.forEach((item) => { |
|
|
item.id = `$DTrain_${item.dataset_id}_${item.dataset_version}`; |
|
|
|
|
|
item.model_type = NodeType.trainDataset; |
|
|
|
|
|
item.style = getStyle(NodeType.trainDataset); |
|
|
|
|
|
|
|
|
item.id = `$DTrain_${id}_${item.dataset_id}_${item.dataset_version}`; |
|
|
|
|
|
item.model_type = NodeType.TrainDataset; |
|
|
|
|
|
item.style = getStyle(NodeType.TrainDataset); |
|
|
}); |
|
|
}); |
|
|
test_dataset.forEach((item) => { |
|
|
test_dataset.forEach((item) => { |
|
|
item.id = `$DTest_${item.dataset_id}_${item.dataset_version}`; |
|
|
|
|
|
item.model_type = NodeType.testDataset; |
|
|
|
|
|
item.style = getStyle(NodeType.testDataset); |
|
|
|
|
|
|
|
|
item.id = `$DTest_${id}_${item.dataset_id}_${item.dataset_version}`; |
|
|
|
|
|
item.model_type = NodeType.TestDataset; |
|
|
|
|
|
item.style = getStyle(NodeType.TestDataset); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
datasetNodes.length = 0; |
|
|
datasetNodes.length = 0; |
|
|
@@ -243,7 +278,7 @@ const addDatasetDependency = ( |
|
|
fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize); |
|
|
fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize); |
|
|
|
|
|
|
|
|
const half = len / 2 - 0.5; |
|
|
const half = len / 2 - 0.5; |
|
|
node.x = currentNode.x! - (half - index) * (ellipseWidth + 20); |
|
|
|
|
|
|
|
|
node.x = currentNode.x! - (half - index) * (ellipseWidth + datasetHGap); |
|
|
node.y = currentNode.y! - nodeHeight - vGap; |
|
|
node.y = currentNode.y! - nodeHeight - vGap; |
|
|
nodes.push(node); |
|
|
nodes.push(node); |
|
|
datasetNodes.push(node); |
|
|
datasetNodes.push(node); |
|
|
@@ -264,14 +299,14 @@ const addProjectDependency = ( |
|
|
nodes: NodeConfig[], |
|
|
nodes: NodeConfig[], |
|
|
edges: EdgeConfig[], |
|
|
edges: EdgeConfig[], |
|
|
) => { |
|
|
) => { |
|
|
const { project_dependency } = data; |
|
|
|
|
|
|
|
|
const { project_dependency, id } = data; |
|
|
if (project_dependency?.url) { |
|
|
if (project_dependency?.url) { |
|
|
const node = { ...project_dependency }; |
|
|
const node = { ...project_dependency }; |
|
|
node.id = `$P_${node.url}_${node.branch}`; |
|
|
|
|
|
node.model_type = NodeType.project; |
|
|
|
|
|
|
|
|
node.id = `$P_${id}_${node.url}_${node.branch}`; |
|
|
|
|
|
node.model_type = NodeType.Project; |
|
|
node.type = 'rect'; |
|
|
node.type = 'rect'; |
|
|
node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize); |
|
|
node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize); |
|
|
node.style = getStyle(NodeType.project); |
|
|
|
|
|
|
|
|
node.style = getStyle(NodeType.Project); |
|
|
node.style.radius = nodeHeight / 2; |
|
|
node.style.radius = nodeHeight / 2; |
|
|
node.x = currentNode.x; |
|
|
node.x = currentNode.x; |
|
|
node.y = currentNode.y! + nodeHeight + vGap; |
|
|
node.y = currentNode.y! + nodeHeight + vGap; |
|
|
@@ -331,3 +366,49 @@ function adjustDatasetPosition(node: NodeConfig) { |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 层级遍历树结构 |
|
|
|
|
|
export function traverseHierarchically(data: ModelDepsData | undefined): ModelDepsData[] { |
|
|
|
|
|
if (!data) return []; |
|
|
|
|
|
let level = 0; |
|
|
|
|
|
data.level = level; |
|
|
|
|
|
const result: ModelDepsData[] = [data]; |
|
|
|
|
|
let index = 0; |
|
|
|
|
|
|
|
|
|
|
|
while (index < result.length) { |
|
|
|
|
|
const item = result[index]; |
|
|
|
|
|
if (item.children) { |
|
|
|
|
|
item.children.forEach((child) => { |
|
|
|
|
|
child.level = item.level + 1; |
|
|
|
|
|
result.push(child); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
index++; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 找到同层次的下一个节点 |
|
|
|
|
|
export function getSameHierarchyNextNode(node: ModelDepsData, nodes: ModelDepsData[]) { |
|
|
|
|
|
const index = nodes.findIndex((item) => item.id === node.id); |
|
|
|
|
|
if (index >= 0 && index < nodes.length - 1) { |
|
|
|
|
|
const nextNode = nodes[index + 1]; |
|
|
|
|
|
if (nextNode.level === node.level) { |
|
|
|
|
|
return nextNode; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 得到层级的宽度 |
|
|
|
|
|
export function getHierarchyWidth(level: number, nodes: ModelDepsData[]) { |
|
|
|
|
|
const hierarchyNodes = nodes |
|
|
|
|
|
.filter((item) => item.level === level && item.expanded === true) |
|
|
|
|
|
.sort((a, b) => b.datasetLen - a.datasetLen); |
|
|
|
|
|
const first = hierarchyNodes[0]; |
|
|
|
|
|
if (first) { |
|
|
|
|
|
return Math.max(((first.datasetLen - 1) * (nodeWidth + datasetHGap)) / 2, 0); |
|
|
|
|
|
} |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |