StructV2/src/Model/modelConstructor.ts

609 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Graph } from '@antv/g6';
import { Group } from '../Common/group';
import { Util } from '../Common/util';
import { Engine } from '../engine';
import {
AddressLabelOption,
IndexLabelOption,
LayoutCreator,
LayoutGroupOptions,
LinkOption,
MarkerOption,
NodeOption,
} from '../options';
import { sourceLinkData, LinkTarget, Sources, SourceNode } from '../sources';
import { SV } from '../StructV';
import { SVLink } from './SVLink';
import { SVModel } from './SVModel';
import { SVNode } from './SVNode';
import { SVAddressLabel, SVFreedLabel, SVIndexLabel, SVMarker, SVNodeAppendage } from './SVNodeAppendage';
export type LayoutGroup = {
name: string;
node: SVNode[];
appendage: SVNodeAppendage[];
link: SVLink[];
layoutCreator: LayoutCreator;
layout: string;
options: LayoutGroupOptions;
modelList: SVModel[];
isHide: boolean;
};
export type LayoutGroupTable = Map<string, LayoutGroup>;
export class ModelConstructor {
private engine: Engine;
private layoutGroupTable: LayoutGroupTable;
private prevSourcesStringMap: { [key: string]: string }; // 保存上一次源数据转换为字符串之后的值,用作比较该次源数据和上一次源数据是否有差异,若相同,则可跳过重复构建过程
constructor(engine: Engine) {
this.engine = engine;
this.prevSourcesStringMap = {};
}
/**
* 构建SVNodeSVLink, SVMarker, SVAddressLabel, SVIndexLabel等
* @param sourceList
*/
public construct(sources: Sources): LayoutGroupTable {
const layoutGroupTable = new Map<string, LayoutGroup>(),
layoutMap: { [key: string]: LayoutCreator } = SV.registeredLayout;
Object.keys(sources).forEach(group => {
let sourceGroup = sources[group],
layout = sourceGroup.layouter,
layoutCreator: LayoutCreator = layoutMap[layout];
if (!layout || !layoutCreator) {
return;
}
let sourceDataString: string = JSON.stringify(sourceGroup.data),
prevString: string = this.prevSourcesStringMap[group],
nodeList: SVNode[] = [],
appendageList: SVNodeAppendage[] = [];
if (prevString === sourceDataString) {
return;
}
const options: LayoutGroupOptions = layoutCreator.defineOptions(sourceGroup.data),
sourceData = layoutCreator.sourcesPreprocess(sourceGroup.data, options, group),
nodeOptions = options.node || options['element'] || {},
markerOptions = options.marker || {},
indexLabelOptions = options.indexLabel || {},
addressLabelOption = options.addressLabel || {};
nodeList = this.constructNodes(group, layout, nodeOptions, sourceData);
appendageList.push(...this.constructMarkers(group, layout, markerOptions, nodeList));
appendageList.push(...this.constructIndexLabel(group, layout, indexLabelOptions, nodeList));
appendageList.push(...this.constructAddressLabel(group, layout, addressLabelOption, nodeList));
nodeList.forEach(item => {
if (item.appendages.freedLabel) {
appendageList.push(...item.appendages.freedLabel);
}
});
layoutGroupTable.set(group, {
name: group,
node: nodeList,
appendage: appendageList,
link: [],
options,
layoutCreator,
modelList: [...nodeList, ...appendageList],
layout,
isHide: false,
});
});
layoutGroupTable.forEach((layoutGroup: LayoutGroup, group: string) => {
const linkOptions = layoutGroup.options.link || {},
linkList: SVLink[] = this.constructLinks(
group,
layoutGroup.layout,
linkOptions,
layoutGroup.node,
layoutGroupTable
);
layoutGroup.link = linkList;
layoutGroup.modelList.push(...linkList);
});
this.layoutGroupTable = layoutGroupTable;
return this.layoutGroupTable;
}
/**
* 从源数据构建 node 集
* @param nodeOptions
* @param group
* @param sourceList
* @param layout
* @returns
*/
private constructNodes(
group: string,
layout: string,
nodeOptions: { [key: string]: NodeOption },
sourceList: SourceNode[]
): SVNode[] {
let defaultSourceNodeType: string = 'default',
nodeList: SVNode[] = [];
sourceList.forEach(item => {
if (item === null) {
return;
}
if (item.type === undefined || item.type === null) {
item.type = defaultSourceNodeType;
}
nodeList.push(this.createNode(item, item.type, group, layout, nodeOptions[item.type]));
});
return nodeList;
}
/**
* 从配置和 node 集构建 link 集
* @param linkOptions
* @param nodes
* @param layoutGroupTable
* @returns
*/
private constructLinks(
group: string,
layout: string,
linkOptions: { [key: string]: LinkOption },
nodes: SVNode[],
layoutGroupTable: LayoutGroupTable
): SVLink[] {
let linkList: SVLink[] = [],
linkNames = Object.keys(linkOptions);
linkNames.forEach(name => {
for (let i = 0; i < nodes.length; i++) {
let node: SVNode = nodes[i],
value,
sourceLinkData: sourceLinkData = node.sourceNode[name],
targetNode: SVNode | SVNode[] = null,
link: SVLink = null;
if (sourceLinkData === undefined || sourceLinkData === null) {
node[name] = null;
continue;
}
// ------------------- 将连接声明字段 sourceLinkData 从 id 变为 SVNode -------------------
if (Array.isArray(sourceLinkData)) {
node[name] = sourceLinkData.map((item, index) => {
// 提取权值
if (typeof item === 'string' && item.includes('/')) {
value = item.split('/')[1];
item = item.split('/')[0];
}
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
let isGeneralLink = ModelConstructor.isGeneralLink(sourceLinkData.toString());
if (targetNode) {
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name],value);
linkList.push(link);
}
return isGeneralLink ? targetNode : null;
});
} else {
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
let isGeneralLink = ModelConstructor.isGeneralLink(sourceLinkData.toString());
if (targetNode) {
link = this.createLink(name, group, layout, node, targetNode, 0, linkOptions[name],value);
linkList.push(link);
}
node[name] = isGeneralLink ? targetNode : null;
}
}
});
return linkList;
}
/**
* 从配置项构建 indexLabel 集
* @param group
* @param layout
* @param indexLabelOptions
*/
private constructIndexLabel(
group: string,
layout: string,
indexLabelOptions: { [key: string]: IndexLabelOption },
nodes: SVNode[]
): SVIndexLabel[] {
let indexLabelList: SVIndexLabel[] = [],
indexNames = Object.keys(indexLabelOptions);
indexNames.forEach(name => {
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i],
value = node[name];
// 若没有指针字段的结点则跳过
if (value === undefined || value === null) continue;
let id = `${group}[${name}(${value}-${node.id})]`,
indexLabel = new SVIndexLabel(
id,
name,
group,
layout,
value.toString(),
node,
indexLabelOptions[name]
);
indexLabelList.push(indexLabel);
}
});
return indexLabelList;
}
/**
*
* @param group
* @param layout
* @param addressLabelOption
* @param nodes
*/
private constructAddressLabel(
group: string,
layout: string,
addressLabelOption: AddressLabelOption,
nodes: SVNode[]
): SVAddressLabel[] {
let addressLabelList: SVAddressLabel[] = [];
nodes.forEach(item => {
const addressLabel = new SVAddressLabel(
`address-label(${item.id})`,
item.sourceType,
group,
layout,
item,
addressLabelOption
);
addressLabelList.push(addressLabel);
});
return addressLabelList;
}
/**
* 从配置和 node 集构建 marker 集
* @param markerOptions
* @param nodes
* @returns
*/
private constructMarkers(
group: string,
layout: string,
markerOptions: { [key: string]: MarkerOption },
nodes: SVNode[]
): SVMarker[] {
let markerList: SVMarker[] = [],
markerNames = Object.keys(markerOptions);
markerNames.forEach(name => {
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i],
markerData = node[name];
// 若没有指针字段的结点则跳过
if (!markerData) continue;
let id = `[${name}(${Array.isArray(markerData) ? markerData.join('-') : markerData})]`,
marker = new SVMarker(id, name, group, layout, markerData, node, markerOptions[name]);
markerList.push(marker);
}
});
return markerList;
}
/**
* 求解label文本
* @param label
* @param sourceNode
*/
private resolveNodeLabel(label: string | string[], sourceNode: SourceNode): string {
let targetLabel: any = '';
if (Array.isArray(label)) {
targetLabel = label.map(item => this.parserNodeContent(sourceNode, item) ?? '');
} else {
targetLabel = this.parserNodeContent(sourceNode, label);
}
if (targetLabel === 'undefined') {
targetLabel = '';
}
return targetLabel ?? '';
}
/**
* 元素工厂,创建 Node
* @param sourceNode
* @param sourceNodeType
* @param group
* @param layout
* @param options
*/
private createNode(
sourceNode: SourceNode,
sourceNodeType: string,
group: string,
layout: string,
options: NodeOption
): SVNode {
let label: string | string[] = this.resolveNodeLabel(options.label, sourceNode),
id = `${sourceNodeType}(${sourceNode.id.toString()})`,
node = new SVNode(id, sourceNodeType, group, layout, sourceNode, label, options);
if (node.freed) {
new SVFreedLabel(`freed-label(${id})`, sourceNodeType, group, layout, node);
}
return node;
}
/**
* 连线工厂创建Link
* @param linkName
* @param group
* @param layout
* @param node
* @param target
* @param index
* @param options
* @returns
*/
private createLink(
linkName: string,
group: string,
layout: string,
node: SVNode,
target: SVNode,
index: number,
options: LinkOption,
label: string
): SVLink {
let id;
// 如果数据结构是图则不需要index来区分是哪条边
if (layout.indexOf('Graph') !== -1) {
id = `{${node.id}-${target.id}}`;
} else {
id = `${linkName}{${node.id}-${target.id}}#${index}`;
}
return new SVLink(id, linkName, group, layout, node, target, index, options,label);
}
/**
* 解析元素文本内容
* @param sourceNode
* @param formatLabel
*/
private parserNodeContent(sourceNode: SourceNode, formatLabel: string): string {
let fields = Util.textParser(formatLabel);
if (Array.isArray(fields)) {
let values = fields.map(item => sourceNode[item]);
values.map((item, index) => {
formatLabel = formatLabel.replace('[' + fields[index] + ']', item);
});
}
return formatLabel;
}
/**
* 由source中的连接字段获取真实的连接目标元素
* @param nodeContainer
* @param node
* @param linkTarget
*/
private fetchTargetNodes(layoutGroupTable: LayoutGroupTable, node: SVNode, linkTarget: LinkTarget): SVNode {
let group: string = node.group,
sourceNodeType = node.sourceType,
nodeList: SVNode[],
targetId = linkTarget,
targetGroupName = group,
targetNode = null;
if (linkTarget === null || linkTarget === undefined) {
return null;
}
if (typeof linkTarget === 'number' || (typeof linkTarget === 'string' && !linkTarget.includes('#'))) {
linkTarget = 'default#' + linkTarget;
}
let info = linkTarget.split('#');
targetId = info.pop();
if (info.length > 1) {
sourceNodeType = info.pop();
targetGroupName = info.pop();
} else {
let field = info.pop();
if (layoutGroupTable.get(targetGroupName).node.find(item => item.sourceType === field)) {
sourceNodeType = field;
} else if (layoutGroupTable.has(field)) {
targetGroupName = field;
} else {
return null;
}
}
// 为了可以连接到不同group的结点
for (let layoutGroup of layoutGroupTable.values()) {
nodeList = layoutGroup.node.filter(item => item.sourceType === sourceNodeType);
if (nodeList === undefined) {
continue;
}
targetNode = nodeList.find(item => item.sourceId === targetId);
if (targetNode) {
break;
}
}
// nodeList = layoutGroupTable.get(targetGroupName).node.filter(item => item.sourceType === sourceNodeType);
// // 若目标node不存在返回null
// if (nodeList === undefined) {
// return null;
// }
// targetNode = nodeList.find(item => item.sourceId === targetId);
return targetNode || null;
}
/**
*
* @returns
*/
public getLayoutGroupTable(): LayoutGroupTable {
return this.layoutGroupTable;
}
/**
* 销毁
*/
public destroy() {
this.layoutGroupTable = null;
this.prevSourcesStringMap = null;
}
// -------------------------------------------------------------------------------------------------
/**
* 检测改指针是否为常规指针指向另一个group
* @param linkId
*/
static isGeneralLink(linkId: string): boolean {
let counter = 0;
for (let i = 0; i < linkId.length; i++) {
if (linkId[i] === '#') {
counter++;
}
}
return counter <= 2;
}
/**
* 判断这个节点是否来自相同group
* @param node1
* @param node2
*/
static isSameGroup(node1: SVNode, node2: SVNode): boolean {
return node1.group === node2.group;
}
/**
* 获取簇
* - 什么为一个簇有边相连的一堆节点再加上这些节点各自的appendages共同组成了一个簇
* @param models
* @returns
*/
static getClusters(models: SVModel[]): Group[] {
const clusterGroupList = [],
idMap = {},
idName = '__clusterId';
models.forEach(item => {
idMap[item.id] = item;
});
const DFS = (model: SVModel, clusterId: number, idMap): SVModel[] => {
if (model === null) {
return [];
}
if (idMap[model.id] === undefined) {
return [];
}
if (model[idName] !== undefined) {
return [];
}
const list = [model];
model[idName] = clusterId;
if (model instanceof SVNode) {
model.getAppendagesList().forEach(item => {
list.push(...DFS(item, clusterId, idMap));
});
model.links.inDegree.forEach(item => {
list.push(...DFS(item, clusterId, idMap));
});
model.links.outDegree.forEach(item => {
list.push(...DFS(item, clusterId, idMap));
});
}
if (model instanceof SVLink) {
list.push(...DFS(model.node, clusterId, idMap));
list.push(...DFS(model.target, clusterId, idMap));
}
if (model instanceof SVNodeAppendage) {
list.push(...DFS(model.target, clusterId, idMap));
}
return list;
};
for (let i = 0; i < models.length; i++) {
const model = models[i];
if (model[idName] !== undefined) {
delete model[idName];
continue;
}
const group = new Group(),
clusterList = DFS(model, i, idMap);
clusterList.forEach(item => {
group.add(item);
});
clusterGroupList.push(group);
delete model[idName];
}
return clusterGroupList;
}
}