| @@ -17,10 +17,10 @@ const filterResourceStandard: SelectProps<string, ComputingResource>['filterOpti | |||||
| const convertId = (item: any) => ({ ...item, id: String(item.id) }); | const convertId = (item: any) => ({ ...item, id: String(item.id) }); | ||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; | |||||
| fieldNames?: SelectProps['fieldNames']; | |||||
| optionFilterProp?: SelectProps['optionFilterProp']; | |||||
| filterOption?: SelectProps['filterOption']; | |||||
| getOptions: () => Promise<any>; // 获取下拉数据 | |||||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | |||||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | |||||
| filterOption?: SelectProps['filterOption']; // 过滤函数 | |||||
| }; | }; | ||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | export const paramSelectConfig: Record<string, SelectPropsConfig> = { | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; | import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| // 获取缓存数据 | |||||
| // 读取缓存数据,组件卸载时清除缓存 | |||||
| export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) { | export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) { | ||||
| const [storage, setStorage] = useState<T>(initialValue); | const [storage, setStorage] = useState<T>(initialValue); | ||||
| @@ -24,32 +24,32 @@ export enum ResourceType { | |||||
| } | } | ||||
| type ResourceTypeInfo = { | type ResourceTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; | |||||
| getVersions: (params: any) => Promise<any>; | |||||
| getFiles: (params: any) => Promise<any>; | |||||
| deleteRecord: (params: any) => Promise<any>; | |||||
| addVersion: (params: any) => Promise<any>; | |||||
| deleteVersion: (params: any) => Promise<any>; | |||||
| getInfo: (params: any) => Promise<any>; | |||||
| name: string; | |||||
| typeParamKey: string; | |||||
| tagParamKey: string; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||||
| tabItems: TabsProps['items']; | |||||
| typeTitle: string; | |||||
| tagTitle: string; | |||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | |||||
| getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表 | |||||
| deleteRecord: (params: any) => Promise<any>; // 删除 | |||||
| addVersion: (params: any) => Promise<any>; // 新增版本 | |||||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | |||||
| getInfo: (params: any) => Promise<any>; // 获取详情 | |||||
| name: string; // 名称 | |||||
| typeParamKey: string; // 类型参数名称,获取资源列表接口使用 | |||||
| tagParamKey: string; // 标签参数名称,获取资源列表接口使用 | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| tabItems: TabsProps['items']; // tab 列表 | |||||
| typeTitle: string; // 类型标题 | |||||
| tagTitle: string; // 标签标题 | |||||
| typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | ||||
| tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | ||||
| prefix: string; // 前缀 | |||||
| prefix: string; // 图片资源、详情 url 的前缀 | |||||
| deleteModalTitle: string; // 删除弹框的title | deleteModalTitle: string; // 删除弹框的title | ||||
| addBtnTitle: string; // 新增按钮的title | addBtnTitle: string; // 新增按钮的title | ||||
| idParamKey: 'models_id' | 'dataset_id'; | |||||
| uploadAction: string; | |||||
| uploadAccept?: string; | |||||
| downloadAllAction: string; | |||||
| downloadSingleAction: string; | |||||
| infoTypePropertyName: string; | |||||
| infoTagPropertyName: string; | |||||
| idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称 | |||||
| uploadAction: string; // 上传接口 url | |||||
| uploadAccept?: string; // 上传文件类型 | |||||
| downloadAllAction: string; // 批量下载接口 url | |||||
| downloadSingleAction: string; // 单个下载接口 url | |||||
| infoTypePropertyName: string; // 详情数据中,类型属性名称 | |||||
| infoTagPropertyName: string; // 详情数据中,标签属性名称 | |||||
| }; | }; | ||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | ||||
| @@ -16,19 +16,19 @@ function GraphLegand({ style }: GraphLegandProps) { | |||||
| const legends: GraphLegandData[] = [ | const legends: GraphLegandData[] = [ | ||||
| { | { | ||||
| name: '父模型', | name: '父模型', | ||||
| color: '#76b1ff', | |||||
| color: 'linear-gradient(305deg,#43c9b1 0%,#93dfd1 100%)', | |||||
| radius: 2, | radius: 2, | ||||
| fill: true, | fill: true, | ||||
| }, | }, | ||||
| { | { | ||||
| name: '当前模型', | name: '当前模型', | ||||
| color: '#1664ff', | |||||
| color: 'linear-gradient(139.97deg,#72a1ff 0%,#1664ff 100%)', | |||||
| radius: 2, | radius: 2, | ||||
| fill: true, | fill: true, | ||||
| }, | }, | ||||
| { | { | ||||
| name: '衍生模型', | name: '衍生模型', | ||||
| color: '#b7cfff', | |||||
| color: 'linear-gradient(139.97deg,#72b4ff 0%,#169aff 100%)', | |||||
| radius: 2, | radius: 2, | ||||
| fill: true, | fill: true, | ||||
| }, | }, | ||||
| @@ -42,7 +42,7 @@ function GraphLegand({ style }: GraphLegandProps) { | |||||
| width: '16px', | width: '16px', | ||||
| height: '12px', | height: '12px', | ||||
| borderRadius: item.radius, | borderRadius: item.radius, | ||||
| backgroundColor: item.color, | |||||
| background: item.color, | |||||
| }} | }} | ||||
| ></div> | ></div> | ||||
| <div className={styles['graph-legend__item__name']}>{item.name}</div> | <div className={styles['graph-legend__item__name']}>{item.name}</div> | ||||
| @@ -2,264 +2,16 @@ import { useEffectWhen } from '@/hooks'; | |||||
| import { ResourceVersionData } from '@/pages/Dataset/config'; | import { ResourceVersionData } from '@/pages/Dataset/config'; | ||||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | import { getModelAtlasReq } from '@/services/dataset/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { changePropertyName, fittingString } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { | |||||
| EdgeConfig, | |||||
| G6GraphEvent, | |||||
| Graph, | |||||
| GraphData, | |||||
| LayoutConfig, | |||||
| NodeConfig, | |||||
| TreeGraphData, | |||||
| Util, | |||||
| } from '@antv/g6'; | |||||
| import G6, { G6GraphEvent, Graph, Item } from '@antv/g6'; | |||||
| // @ts-ignore | // @ts-ignore | ||||
| import Hierarchy from '@antv/hierarchy'; | |||||
| import { Flex, Select } from 'antd'; | import { Flex, Select } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import GraphLegand from '../GraphLegand'; | import GraphLegand from '../GraphLegand'; | ||||
| import NodeTooltips from '../NodeTooltips'; | import NodeTooltips from '../NodeTooltips'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const nodeWidth = 98; | |||||
| const nodeHeight = 58; | |||||
| const vGap = 58; | |||||
| const hGap = 58; | |||||
| enum NodeType { | |||||
| current = 'current', | |||||
| parent = 'parent', | |||||
| children = 'children', | |||||
| project = 'project', | |||||
| trainDataset = 'trainDataset', | |||||
| testDataset = 'testDataset', | |||||
| } | |||||
| type TrainTask = { | |||||
| ins_id: number; | |||||
| name: string; | |||||
| task_id: string; | |||||
| }; | |||||
| interface TrainDataset extends NodeConfig { | |||||
| dataset_id: number; | |||||
| dataset_name: string; | |||||
| dataset_version: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| interface ProjectDependency extends NodeConfig { | |||||
| url: string; | |||||
| name: string; | |||||
| branch: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| type ModalDetail = { | |||||
| name: string; | |||||
| available_range: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| model_type_name: string; | |||||
| model_tag_name: string; | |||||
| create_time: string; | |||||
| }; | |||||
| interface ModelDepsAPIData { | |||||
| current_model_id: number; | |||||
| version: string; | |||||
| exp_ins_id: number; | |||||
| model_type: NodeType; | |||||
| current_model_name: string; | |||||
| project_dependency: ProjectDependency; | |||||
| test_dataset: TrainDataset[]; | |||||
| train_dataset: TrainDataset[]; | |||||
| train_task: TrainTask; | |||||
| model_version_dependcy_vo: ModalDetail; | |||||
| children_models: ModelDepsAPIData[]; | |||||
| parent_models: ModelDepsAPIData[]; | |||||
| } | |||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { | |||||
| children: ModelDepsData[]; | |||||
| } | |||||
| // 规范化子数据 | |||||
| function normalizeChildren(data: ModelDepsData[]) { | |||||
| if (Array.isArray(data)) { | |||||
| data.forEach((item) => { | |||||
| item.model_type = NodeType.children; | |||||
| item.id = `$M_${item.current_model_id}_${item.version}`; | |||||
| item.label = getLabel(item); | |||||
| item.style = getStyle(NodeType.children); | |||||
| normalizeChildren(item.children); | |||||
| }); | |||||
| } | |||||
| } | |||||
| // 获取 label | |||||
| function getLabel(node: { current_model_name: string; version: string }) { | |||||
| return ( | |||||
| fittingString(`${node.current_model_name}`, 87, 8) + | |||||
| '\n' + | |||||
| fittingString(`${node.version}`, 87, 8) | |||||
| ); | |||||
| } | |||||
| // 获取 style | |||||
| function getStyle(model_type: NodeType) { | |||||
| let fill = ''; | |||||
| switch (model_type) { | |||||
| case NodeType.current: | |||||
| fill = '#1664ff'; | |||||
| break; | |||||
| case NodeType.parent: | |||||
| fill = '#76b1ff'; | |||||
| break; | |||||
| case NodeType.children: | |||||
| fill = '#b7cfff'; | |||||
| break; | |||||
| case NodeType.project: | |||||
| fill = '#FA8C16'; | |||||
| break; | |||||
| case NodeType.trainDataset: | |||||
| fill = '#ff0000'; | |||||
| break; | |||||
| case NodeType.testDataset: | |||||
| fill = '#ff00ff'; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return { | |||||
| fill, | |||||
| }; | |||||
| } | |||||
| // 将后台返回的数据转换成树形数据 | |||||
| function normalizeTreeData(apiData: ModelDepsAPIData, currentNodeName: string): ModelDepsData { | |||||
| // 将 children_models 转换成 children | |||||
| let normalizedData = changePropertyName(apiData, { | |||||
| children_models: 'children', | |||||
| }) as ModelDepsData; | |||||
| // 设置当前模型的数据 | |||||
| normalizedData.model_type = NodeType.current; | |||||
| normalizedData.current_model_name = currentNodeName; | |||||
| normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; | |||||
| normalizedData.label = getLabel(normalizedData); | |||||
| normalizedData.style = getStyle(NodeType.current); | |||||
| normalizeChildren(normalizedData.children as ModelDepsData[]); | |||||
| // 将 parent_models 转换成树形结构 | |||||
| let parent_models = normalizedData.parent_models || []; | |||||
| while (parent_models.length > 0) { | |||||
| const parent = parent_models[0]; | |||||
| normalizedData = { | |||||
| ...parent, | |||||
| model_type: NodeType.parent, | |||||
| id: `$M_${parent.current_model_id}_${parent.version}`, | |||||
| label: getLabel(parent), | |||||
| style: getStyle(NodeType.parent), | |||||
| children: [ | |||||
| { | |||||
| ...normalizedData, | |||||
| parent_models: [], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| parent_models = normalizedData.parent_models || []; | |||||
| } | |||||
| return normalizedData; | |||||
| } | |||||
| // 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 | |||||
| function getGraphData(data: ModelDepsData): GraphData { | |||||
| const config = { | |||||
| direction: 'LR', | |||||
| getHeight: () => nodeHeight, | |||||
| getWidth: () => nodeWidth, | |||||
| getVGap: () => vGap / 2, | |||||
| getHGap: () => hGap / 2, | |||||
| }; | |||||
| // 树形布局计算出坐标 | |||||
| const treeLayoutData: LayoutConfig = Hierarchy['compactBox'](data, config); | |||||
| const nodes: NodeConfig[] = []; | |||||
| const edges: EdgeConfig[] = []; | |||||
| Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { | |||||
| const data = node.data as ModelDepsData; | |||||
| nodes.push({ | |||||
| ...data, | |||||
| x: node.x, | |||||
| y: node.y, | |||||
| }); | |||||
| if (parent) { | |||||
| edges.push({ | |||||
| source: parent.id, | |||||
| target: node.id, | |||||
| }); | |||||
| } | |||||
| // 当前模型显示数据集和项目 | |||||
| if (data.model_type === NodeType.current) { | |||||
| const { project_dependency, train_dataset, test_dataset } = data; | |||||
| train_dataset.forEach((item) => { | |||||
| item.id = `$DTrain_${item.dataset_id}`; | |||||
| item.model_type = NodeType.trainDataset; | |||||
| item.type = 'ellipse'; | |||||
| item.label = fittingString(`${item.dataset_name}`, 87, 8); | |||||
| item.style = getStyle(NodeType.trainDataset); | |||||
| }); | |||||
| test_dataset.forEach((item) => { | |||||
| item.id = `$DTest_${item.dataset_id}`; | |||||
| item.model_type = NodeType.testDataset; | |||||
| item.type = 'ellipse'; | |||||
| item.label = fittingString(item.dataset_name, 87, 8); | |||||
| item.style = getStyle(NodeType.testDataset); | |||||
| }); | |||||
| const len = train_dataset.length + test_dataset.length; | |||||
| [...train_dataset, ...test_dataset].forEach((item, index) => { | |||||
| const half = len / 2 - 0.5; | |||||
| item.x = node.x! - (half - index) * (nodeWidth + hGap); | |||||
| item.y = node.y! - nodeHeight - vGap; | |||||
| nodes.push(item); | |||||
| edges.push({ | |||||
| source: node.id, | |||||
| target: item.id, | |||||
| sourceAnchor: 2, | |||||
| targetAnchor: 3, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| }); | |||||
| if (project_dependency?.url) { | |||||
| project_dependency.id = `$P_${project_dependency.url}`; | |||||
| project_dependency.model_type = NodeType.project; | |||||
| project_dependency.type = 'rect'; | |||||
| project_dependency.size = [nodeHeight, nodeHeight]; | |||||
| project_dependency.label = fittingString(project_dependency.name, 48, 8); | |||||
| project_dependency.style = getStyle(NodeType.project); | |||||
| project_dependency.x = node.x; | |||||
| project_dependency.y = node.y! + nodeHeight + vGap; | |||||
| nodes.push(project_dependency); | |||||
| edges.push({ | |||||
| source: node.id, | |||||
| target: project_dependency.id, | |||||
| sourceAnchor: 3, | |||||
| targetAnchor: 2, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| } | |||||
| } | |||||
| }); | |||||
| return { nodes, edges }; | |||||
| } | |||||
| import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; | |||||
| import { NodeType, getGraphData, nodeHeight, nodeWidth, normalizeTreeData } from './utils'; | |||||
| type modeModelEvolutionProps = { | type modeModelEvolutionProps = { | ||||
| resourceId: number; | resourceId: number; | ||||
| @@ -361,14 +113,15 @@ function ModelEvolution({ | |||||
| default: [ | default: [ | ||||
| 'drag-canvas', | 'drag-canvas', | ||||
| 'zoom-canvas', | 'zoom-canvas', | ||||
| // { | |||||
| // type: 'collapse-expand', | |||||
| // onChange(item?: Item, collapsed?: boolean) { | |||||
| // const data = item!.getModel(); | |||||
| // data.collapsed = collapsed; | |||||
| // return true; | |||||
| // }, | |||||
| // }, | |||||
| 'drag-node', | |||||
| { | |||||
| type: 'collapse-expand', | |||||
| onChange(item?: Item, collapsed?: boolean) { | |||||
| const data = item!.getModel(); | |||||
| data.collapsed = collapsed; | |||||
| return true; | |||||
| }, | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| }); | }); | ||||
| @@ -392,16 +145,18 @@ function ModelEvolution({ | |||||
| return; | return; | ||||
| } | } | ||||
| const point = graph.getCanvasByPoint(x!, y!); | const point = graph.getCanvasByPoint(x!, y!); | ||||
| const zoom = graph.getZoom(); | |||||
| // 更加缩放,调整 tooltip 位置 | |||||
| const offsetX = (nodeWidth * zoom) / 4; | |||||
| const offsetY = (nodeHeight * zoom) / 4; | |||||
| const canvasWidth = graphRef.current!.clientWidth; | const canvasWidth = graphRef.current!.clientWidth; | ||||
| if (point.x + 300 > canvasWidth) { | if (point.x + 300 > canvasWidth) { | ||||
| point.x = canvasWidth - 300; | point.x = canvasWidth - 300; | ||||
| } | } | ||||
| const zoom = graph.getZoom(); | |||||
| // 更加缩放,调整 tooltip 位置 | |||||
| const offsetY = (nodeHeight * zoom) / 4; | |||||
| setHoverNodeData(model); | setHoverNodeData(model); | ||||
| setNodeToolTipX(point.x); | |||||
| setNodeToolTipX(point.x + offsetX); | |||||
| // 92: 版本选择器的高度,296: tooltip的高度 | // 92: 版本选择器的高度,296: tooltip的高度 | ||||
| setNodeToolTipY(point.y + 92 - 296 - offsetY); | setNodeToolTipY(point.y + 92 - 296 - offsetY); | ||||
| setShowNodeTooltip(true); | setShowNodeTooltip(true); | ||||
| @@ -0,0 +1,344 @@ | |||||
| import { changePropertyName, fittingString } from '@/utils'; | |||||
| import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6'; | |||||
| // @ts-ignore | |||||
| import Hierarchy from '@antv/hierarchy'; | |||||
| export const nodeWidth = 110; | |||||
| export const nodeHeight = 50; | |||||
| export const vGap = nodeHeight + 20; | |||||
| export const hGap = nodeHeight + 20; | |||||
| export const ellipseWidth = nodeWidth; | |||||
| // 数据集节点矩形数组 | |||||
| const datasetRects: Rect[] = []; | |||||
| export enum NodeType { | |||||
| current = 'current', | |||||
| parent = 'parent', | |||||
| children = 'children', | |||||
| project = 'project', | |||||
| trainDataset = 'trainDataset', | |||||
| testDataset = 'testDataset', | |||||
| } | |||||
| export type Rect = { | |||||
| x: number; | |||||
| y: number; | |||||
| width: number; | |||||
| height: number; | |||||
| }; | |||||
| export type TrainTask = { | |||||
| ins_id: number; | |||||
| name: string; | |||||
| task_id: string; | |||||
| }; | |||||
| export interface TrainDataset extends NodeConfig { | |||||
| dataset_id: number; | |||||
| dataset_name: string; | |||||
| dataset_version: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| export interface ProjectDependency extends NodeConfig { | |||||
| url: string; | |||||
| name: string; | |||||
| branch: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| export type ModalDetail = { | |||||
| name: string; | |||||
| available_range: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| model_type_name: string; | |||||
| model_tag_name: string; | |||||
| create_time: string; | |||||
| }; | |||||
| export interface ModelDepsAPIData { | |||||
| current_model_id: number; | |||||
| version: string; | |||||
| exp_ins_id: number; | |||||
| model_type: NodeType; | |||||
| current_model_name: string; | |||||
| project_dependency: ProjectDependency; | |||||
| test_dataset: TrainDataset[]; | |||||
| train_dataset: TrainDataset[]; | |||||
| train_task: TrainTask; | |||||
| model_version_dependcy_vo: ModalDetail; | |||||
| children_models: ModelDepsAPIData[]; | |||||
| parent_models: ModelDepsAPIData[]; | |||||
| } | |||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { | |||||
| children: ModelDepsData[]; | |||||
| } | |||||
| // 规范化子数据 | |||||
| export function normalizeChildren(data: ModelDepsData[]) { | |||||
| if (Array.isArray(data)) { | |||||
| data.forEach((item) => { | |||||
| item.model_type = NodeType.children; | |||||
| item.id = `$M_${item.current_model_id}_${item.version}`; | |||||
| item.label = getLabel(item); | |||||
| item.style = getStyle(NodeType.children); | |||||
| normalizeChildren(item.children); | |||||
| }); | |||||
| } | |||||
| } | |||||
| // 获取 label | |||||
| export function getLabel(node: { current_model_name: string; version: string }) { | |||||
| return ( | |||||
| fittingString(`${node.current_model_name}`, nodeWidth - 12, 8) + | |||||
| '\n' + | |||||
| fittingString(`${node.version}`, nodeWidth - 12, 8) | |||||
| ); | |||||
| } | |||||
| // 获取 style | |||||
| export function getStyle(model_type: NodeType) { | |||||
| let fill = ''; | |||||
| switch (model_type) { | |||||
| case NodeType.current: | |||||
| fill = 'l(0) 0:#72a1ff 1:#1664ff'; | |||||
| break; | |||||
| case NodeType.parent: | |||||
| fill = 'l(0) 0:#93dfd1 1:#43c9b1'; | |||||
| break; | |||||
| case NodeType.children: | |||||
| fill = 'l(0) 0:#72b4ff 1:#169aff'; | |||||
| break; | |||||
| case NodeType.project: | |||||
| fill = 'l(0) 0:#b3a9ff 1:#8981ff'; | |||||
| break; | |||||
| case NodeType.trainDataset: | |||||
| fill = '#a5d878'; | |||||
| break; | |||||
| case NodeType.testDataset: | |||||
| fill = '#d8b578'; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return { | |||||
| fill, | |||||
| }; | |||||
| } | |||||
| // 将后台返回的数据转换成树形数据 | |||||
| export function normalizeTreeData( | |||||
| apiData: ModelDepsAPIData, | |||||
| currentNodeName: string, | |||||
| ): ModelDepsData { | |||||
| // 将 children_models 转换成 children | |||||
| let normalizedData = changePropertyName(apiData, { | |||||
| children_models: 'children', | |||||
| }) as ModelDepsData; | |||||
| // 设置当前模型的数据 | |||||
| normalizedData.model_type = NodeType.current; | |||||
| normalizedData.current_model_name = currentNodeName; | |||||
| normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; | |||||
| normalizedData.label = getLabel({ | |||||
| current_model_name: currentNodeName, | |||||
| version: normalizedData.version, | |||||
| }); | |||||
| normalizedData.style = getStyle(NodeType.current); | |||||
| // let first1 = { ...normalizedData.children[0] }; | |||||
| // let first2 = { ...normalizedData.children[0] }; | |||||
| // let first3 = { ...normalizedData.children[0] }; | |||||
| // first1.current_model_id = 202020; | |||||
| // first2.current_model_id = 202021; | |||||
| // first3.current_model_id = 202022; | |||||
| // normalizedData.children.push(first1, first2, first3); | |||||
| normalizeChildren(normalizedData.children as ModelDepsData[]); | |||||
| // 将 parent_models 转换成树形结构 | |||||
| let parent_models = normalizedData.parent_models || []; | |||||
| while (parent_models.length > 0) { | |||||
| const parent = parent_models[0]; | |||||
| normalizedData = { | |||||
| ...parent, | |||||
| model_type: NodeType.parent, | |||||
| id: `$M_${parent.current_model_id}_${parent.version}`, | |||||
| label: getLabel(parent), | |||||
| style: getStyle(NodeType.parent), | |||||
| children: [ | |||||
| { | |||||
| ...normalizedData, | |||||
| parent_models: [], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| parent_models = normalizedData.parent_models || []; | |||||
| } | |||||
| return normalizedData; | |||||
| } | |||||
| // 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 | |||||
| export function getGraphData(data: ModelDepsData): GraphData { | |||||
| const config = { | |||||
| direction: 'LR', | |||||
| getHeight: () => nodeHeight, | |||||
| getWidth: () => nodeWidth, | |||||
| getVGap: () => vGap / 2, | |||||
| getHGap: () => hGap / 2, | |||||
| }; | |||||
| // 树形布局计算出坐标 | |||||
| const treeLayoutData: LayoutConfig = Hierarchy['compactBox'](data, config); | |||||
| const nodes: NodeConfig[] = []; | |||||
| const edges: EdgeConfig[] = []; | |||||
| Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { | |||||
| const data = node.data as ModelDepsData; | |||||
| console.log('data', data); | |||||
| // 当前模型显示数据集和项目 | |||||
| if (data.model_type === NodeType.current) { | |||||
| addDatasetDependency(data, node, nodes, edges); | |||||
| addProjectDependency(data, node, nodes, edges); | |||||
| } else if (data.model_type === NodeType.children) { | |||||
| adjustChildrenPosition(node); | |||||
| } | |||||
| nodes.push({ | |||||
| ...data, | |||||
| x: node.x, | |||||
| y: node.y, | |||||
| }); | |||||
| if (parent) { | |||||
| edges.push({ | |||||
| source: parent.id, | |||||
| target: node.id, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| return { nodes, edges }; | |||||
| } | |||||
| // 将数据集转换成 G6 的数据 | |||||
| const addDatasetDependency = ( | |||||
| data: ModelDepsData, | |||||
| currentNode: NodeConfig, | |||||
| nodes: NodeConfig[], | |||||
| edges: EdgeConfig[], | |||||
| ) => { | |||||
| const { train_dataset, test_dataset } = data; | |||||
| train_dataset.forEach((item) => { | |||||
| item.id = `$DTrain_${item.dataset_id}`; | |||||
| item.model_type = NodeType.trainDataset; | |||||
| item.style = getStyle(NodeType.trainDataset); | |||||
| }); | |||||
| test_dataset.forEach((item) => { | |||||
| item.id = `$DTest_${item.dataset_id}`; | |||||
| item.model_type = NodeType.testDataset; | |||||
| item.style = getStyle(NodeType.testDataset); | |||||
| }); | |||||
| datasetRects.length = 0; | |||||
| const len = train_dataset.length + test_dataset.length; | |||||
| [...train_dataset, ...test_dataset].forEach((item, index) => { | |||||
| const node = { ...item }; | |||||
| node.type = 'ellipse'; | |||||
| node.size = [ellipseWidth, nodeHeight]; | |||||
| node.label = | |||||
| fittingString(node.dataset_name, ellipseWidth - 12, 8) + | |||||
| '\n' + | |||||
| fittingString(node.dataset_version, ellipseWidth - 12, 8); | |||||
| const half = len / 2 - 0.5; | |||||
| node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap); | |||||
| node.y = currentNode.y! - nodeHeight - vGap; | |||||
| nodes.push(node); | |||||
| edges.push({ | |||||
| source: currentNode.id, | |||||
| target: node.id, | |||||
| sourceAnchor: 2, | |||||
| targetAnchor: 3, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| datasetRects.push({ | |||||
| x: node.x - ellipseWidth / 2, | |||||
| y: node.y - nodeHeight / 2, | |||||
| width: ellipseWidth, | |||||
| height: nodeHeight, | |||||
| }); | |||||
| }); | |||||
| }; | |||||
| // 将模型依赖数据转换成 G6 的数据 | |||||
| const addProjectDependency = ( | |||||
| data: ModelDepsData, | |||||
| currentNode: NodeConfig, | |||||
| nodes: NodeConfig[], | |||||
| edges: EdgeConfig[], | |||||
| ) => { | |||||
| const { project_dependency } = data; | |||||
| if (project_dependency?.url) { | |||||
| const node = { ...project_dependency }; | |||||
| node.id = `$P_${node.url}`; | |||||
| node.model_type = NodeType.project; | |||||
| node.type = 'rect'; | |||||
| node.label = fittingString(node.name, nodeWidth - 12, 8); | |||||
| node.style = getStyle(NodeType.project); | |||||
| node.style.radius = nodeHeight / 2; | |||||
| node.x = currentNode.x; | |||||
| node.y = currentNode.y! + nodeHeight + vGap; | |||||
| nodes.push(node); | |||||
| edges.push({ | |||||
| source: currentNode.id, | |||||
| target: node.id, | |||||
| sourceAnchor: 3, | |||||
| targetAnchor: 2, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| } | |||||
| }; | |||||
| // 判断两个矩形是否相交 | |||||
| function isRectanglesIntersect(rect1: Rect, rect2: Rect) { | |||||
| return !( | |||||
| rect1.x + rect1.width < rect2.x || | |||||
| rect1.x > rect2.x + rect2.width || | |||||
| rect1.y + rect1.height < rect2.y || | |||||
| rect1.y > rect2.y + rect2.height | |||||
| ); | |||||
| } | |||||
| // 判断子节点是否与数据集节点重叠 | |||||
| function isChildrenIntersectDataset(rects: Rect[], childrenRect: Rect) { | |||||
| for (const r of rects) { | |||||
| if (isRectanglesIntersect(r, childrenRect)) { | |||||
| return r; | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 计算子节点位置 | |||||
| function adjustChildrenPosition(node: NodeConfig) { | |||||
| const nodeRect = { | |||||
| x: node.x! - nodeWidth / 2, | |||||
| y: node.y! - nodeHeight / 2, | |||||
| width: nodeWidth, | |||||
| height: nodeHeight, | |||||
| }; | |||||
| const overlapRect = isChildrenIntersectDataset(datasetRects, nodeRect); | |||||
| if (overlapRect) { | |||||
| const offsetY = nodeRect.y - overlapRect.y; | |||||
| const space = 10; //(vGap + Math.abs(offsetY) - nodeHeight) / 2; | |||||
| if (offsetY >= 0) { | |||||
| node.y = node.y! + (nodeHeight - offsetY + space); | |||||
| } else { | |||||
| node.y = node.y! - (nodeHeight - Math.abs(offsetY) + space); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,5 +1,5 @@ | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { ModelDepsData } from '../ModelEvolution'; | |||||
| import { ModelDepsData } from '../ModelEvolution/utils'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type NodeTooltipsProps = { | type NodeTooltipsProps = { | ||||
| @@ -27,16 +27,16 @@ export type MirrorVersion = { | |||||
| }; | }; | ||||
| export type SelectorTypeInfo = { | export type SelectorTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; | |||||
| getVersions: (params: any) => Promise<any>; | |||||
| getFiles: (params: any) => Promise<any>; | |||||
| handleVersionResponse: (res: any) => any[]; | |||||
| modalIcon: string; | |||||
| buttonIcon: string; | |||||
| name: string; | |||||
| litReqParamKey: 'available_range' | 'image_type'; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||||
| tabItems: TabsProps['items']; | |||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (params: any) => Promise<any>; // 获取资源版本列表 | |||||
| getFiles: (params: any) => Promise<any>; // 获取资源文件列表 | |||||
| handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据 | |||||
| modalIcon: string; // modal icon | |||||
| buttonIcon: string; // button icon | |||||
| name: string; // 名称 | |||||
| litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| tabItems: TabsProps['items']; // tab 列表 | |||||
| }; | }; | ||||
| // 获取镜像文件列表,为了兼容数据集和模型 | // 获取镜像文件列表,为了兼容数据集和模型 | ||||
| @@ -35,13 +35,11 @@ const EditPipeline = () => { | |||||
| }, []); | }, []); | ||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val); | |||||
| const _x = val.x; | const _x = val.x; | ||||
| const _y = val.y; | const _y = val.y; | ||||
| const point = graph.getPointByClient(_x, _y); | const point = graph.getPointByClient(_x, _y); | ||||
| let model = {}; | |||||
| // 元模型 | // 元模型 | ||||
| model = { | |||||
| const model = { | |||||
| ...val, | ...val, | ||||
| x: point.x, | x: point.x, | ||||
| y: point.y, | y: point.y, | ||||