440 lines
14 KiB
TypeScript
440 lines
14 KiB
TypeScript
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 } from "./SVNodeAppendage";
|
||
|
||
|
||
export type LayoutGroup = {
|
||
name: string;
|
||
node: SVNode[];
|
||
indexLabel: SVIndexLabel[];
|
||
freedLabel: SVFreedLabel[];
|
||
addressLabel: SVAddressLabel[];
|
||
link: SVLink[];
|
||
marker: SVMarker[];
|
||
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 = {};
|
||
}
|
||
|
||
/**
|
||
* 构建SVNode,SVLink, SVMarker, SVAddressLabel, SVIndexLabel等
|
||
* @param sourceList
|
||
*/
|
||
public construct(sources: Sources): LayoutGroupTable {
|
||
const layoutGroupTable = new Map<string, LayoutGroup>(),
|
||
layoutMap: { [key: string]: { [key: string]: LayoutCreator } } = SV.registeredLayout;
|
||
|
||
Object.keys(sources).forEach(group => {
|
||
let sourceGroup = sources[group],
|
||
layout = sourceGroup.layouter,
|
||
mode = sourceGroup.mode || 'default',
|
||
layoutCreator: LayoutCreator = layoutMap[layout][mode];
|
||
|
||
if (!layout || !layoutCreator) {
|
||
return;
|
||
}
|
||
|
||
let sourceDataString: string = JSON.stringify(sourceGroup.data),
|
||
prevString: string = this.prevSourcesStringMap[group],
|
||
nodeList: SVNode[] = [],
|
||
freedLabelList: SVFreedLabel[] = [],
|
||
addressLabelList: SVAddressLabel[] = [],
|
||
indexLabelList: SVIndexLabel[] = [],
|
||
markerList: SVMarker[] = [];
|
||
|
||
if (prevString === sourceDataString) {
|
||
return;
|
||
}
|
||
|
||
const options: LayoutGroupOptions = layoutCreator.defineOptions(sourceGroup.data),
|
||
sourceData = layoutCreator.sourcesPreprocess(sourceGroup.data, options),
|
||
nodeOptions = options.node || options['element'] || {},
|
||
markerOptions = options.marker || {},
|
||
indexLabelOptions = options.indexLabel || {},
|
||
addressLabelOption = options.addressLabel || {};
|
||
|
||
nodeList = this.constructNodes(group, layout, nodeOptions, sourceData);
|
||
markerList = this.constructMarkers(group, layout, markerOptions, nodeList);
|
||
indexLabelList = this.constructIndexLabel(group, layout, indexLabelOptions, nodeList);
|
||
addressLabelList = this.constructAddressLabel(group, layout, addressLabelOption, nodeList);
|
||
nodeList.forEach(item => {
|
||
if(item.freedLabel) {
|
||
freedLabelList.push(item.freedLabel);
|
||
}
|
||
});
|
||
|
||
layoutGroupTable.set(group, {
|
||
name: group,
|
||
node: nodeList,
|
||
freedLabel: freedLabelList,
|
||
addressLabel: addressLabelList,
|
||
indexLabel: indexLabelList,
|
||
link: [],
|
||
marker: markerList,
|
||
options: options,
|
||
layoutCreator,
|
||
modelList: [
|
||
...nodeList,
|
||
...markerList,
|
||
...freedLabelList,
|
||
...addressLabelList,
|
||
...indexLabelList
|
||
],
|
||
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;
|
||
}
|
||
|
||
/**
|
||
*
|
||
* @returns
|
||
*/
|
||
public getLayoutGroupTable(): 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],
|
||
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) => {
|
||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, item);
|
||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
||
|
||
if (targetNode) {
|
||
link = this.createLink(name, group, layout, node, targetNode, index, linkOptions[name]);
|
||
linkList.push(link);
|
||
}
|
||
|
||
return isGeneralLink ? targetNode : null;
|
||
});
|
||
}
|
||
else {
|
||
targetNode = this.fetchTargetNodes(layoutGroupTable, node, sourceLinkData);
|
||
let isGeneralLink = this.isGeneralLink(sourceLinkData.toString());
|
||
|
||
if (targetNode) {
|
||
link = this.createLink(name, group, layout, node, targetNode, null, linkOptions[name]);
|
||
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) continue;
|
||
|
||
let id = `${group}.${name}#${value}`,
|
||
indexLabel = new SVIndexLabel(id, name, group, layout, value, 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(`${item.id}-address-label`, 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 = `${group}.${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) {
|
||
node.freedLabel = new SVFreedLabel(`${id}-freed-label`, 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): SVLink {
|
||
let id = `${linkName}(${node.id}-${target.id})`;
|
||
return new SVLink(id, linkName, group, layout, node, target, index, options);
|
||
}
|
||
|
||
/**
|
||
* 解析元素文本内容
|
||
* @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;
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* 检测改指针是否为常规指针(指向另一个group)
|
||
* @param linkId
|
||
*/
|
||
private isGeneralLink(linkId: string): boolean {
|
||
let counter = 0;
|
||
|
||
for (let i = 0; i < linkId.length; i++) {
|
||
if (linkId[i] === '#') {
|
||
counter++;
|
||
}
|
||
}
|
||
|
||
return counter <= 2;
|
||
}
|
||
|
||
/**
|
||
* 销毁
|
||
*/
|
||
destroy() {
|
||
this.layoutGroupTable = null;
|
||
this.prevSourcesStringMap = null;
|
||
}
|
||
}; |