Merge branch 'main' of https://gitlab.com/phenomLi/StructV2 into main

This commit is contained in:
黎智洲 2021-12-22 21:57:34 +08:00
commit 2cdc35ff30
17 changed files with 14316 additions and 123 deletions

View File

@ -14,6 +14,10 @@ SV.registerLayout('Array', {
return sources; return sources;
}, },
defineLeakRule(nodes) {
return [];
},
defineOptions() { defineOptions() {
return { return {
node: { node: {
@ -49,6 +53,9 @@ SV.registerLayout('Array', {
indexLabel: { indexLabel: {
index: { position: 'bottom' }, index: { position: 'bottom' },
indexRight: { position: 'right' } indexRight: { position: 'right' }
},
behavior: {
dragNode: false
} }
}; };
}, },

View File

@ -42,10 +42,10 @@ SV.registerLayout('LinkList', {
} }
}, },
loopNext: { loopNext: {
type: 'arc', type: 'quadratic',
curveOffset: 50, curveOffset: -100,
sourceAnchor: 2, sourceAnchor: 2,
targetAnchor: 4, targetAnchor: 7,
style: { style: {
stroke: '#333', stroke: '#333',
endArrow: 'default', endArrow: 'default',

View File

@ -13,11 +13,15 @@ SV.registerLayout('Stack', {
return sources; return sources;
}, },
defineLeakRule(nodes) {
return [];
},
defineOptions() { defineOptions() {
return { return {
element: { element: {
default: { default: {
type: 'indexed-node', type: 'array-node',
label: '[id]', label: '[id]',
size: [60, 30], size: [60, 30],
style: { style: {
@ -49,6 +53,9 @@ SV.registerLayout('Stack', {
} }
} }
}, },
indexLabel: {
index: { position: 'left' }
},
behavior: { behavior: {
dragNode: false dragNode: false
} }

View File

@ -99,84 +99,123 @@
let data = [{ let data = [{
Array: { "sqStack0": {
data: [
{ id: 1, data: 1, child: [2, 3], external: 'exx' },
{ id: 2, data: 2 },
{ id: 3, data: 3, child: [6, 4] },
{ id: 4, data: 4 },
{ id: 6, data: 6 }
],
layouter: 'BinaryTree'
},
L: {
data: [
{ id: 11, data: 11, next: 22, external: 'tt' },
{ id: 22, data: 22, next: 33 },
{ id: 33, data: 33, next: 44 },
{ id: 44, data: 44, freed: true }
],
layouter: 'LinkList'
},
"ChainHashTable": {
"data": [ "data": [
{ {
"type": "head", "id": "0x617eb5",
"id": "0x618090", "data": "",
"data": "T" "index": 5,
"cursor": "top"
}, },
{ {
"type": "node", "id": "0x617eb4",
"id": "0x618030", "data": "2",
"data": "N" "index": 4
}, },
{ {
"type": "head", "id": "0x617eb3",
"id": "0x6180f0", "data": "6",
"data": "U", "index": 3
"start": "node#0x618030"
}, },
{ {
"type": "node", "id": "0x617eb2",
"id": "0x617ff0", "data": "7",
"data": "V", "index": 2
"next": "node#0x617fd0"
}, },
{ {
"type": "node", "id": "0x617eb1",
"id": "0x617fd0", "data": "9",
"data": "A" "index": 1
}, },
{ {
"type": "head", "id": "0x617eb0",
"id": "0x618010", "data": "1",
"data": "O", "index": 0,
"start": "node#0x617ff0" "external": "S"
},
{
"type": "node",
"id": "0x618050",
"data": "K"
},
{
"type": "head",
"id": "0x6180b0",
"data": "R",
"start": "node#0x618050"
} }
], ],
"layouter": "ChainHashTable" "layouter": "Stack"
} }
}, { }, {
Array: { "sqStack0": {
data: [ "data": [
{ id: 1, data: 1, child: [2, 3], external: 'exx' }, {
{ id: 2, data: 2 }, "id": "0x617eb4",
{ id: 3, data: 3, child: [6, 7] }, "data": "2",
{ id: 7, data: 7 } "index": 4,
], "cursor": "top",
layouter: 'BinaryTree' "type": "default"
},
{
"id": "0x617eb3",
"data": "6",
"index": 3,
"type": "default"
},
{
"id": "0x617eb2",
"data": "7",
"index": 2,
"type": "default"
},
{
"id": "0x617eb1",
"data": "9",
"index": 1,
"type": "default"
},
{
"id": "0x617eb0",
"data": "1",
"index": 0,
"bottomExternal": "S",
"type": "default"
} }
],
"layouter": "Stack"
}
}, {
"data": [
{
"id": "0x617eb5",
"data": "",
"index": 5,
"cursor": "top",
"type": "default"
},
{
"id": "0x617eb4",
"data": "2",
"index": 4,
"type": "default"
},
{
"id": "0x617eb3",
"data": "6",
"index": 3,
"type": "default"
},
{
"id": "0x617eb2",
"data": "7",
"index": 2,
"type": "default"
},
{
"id": "0x617eb1",
"data": "9",
"index": 1,
"type": "default"
},
{
"id": "0x617eb0",
"data": "1",
"index": 0,
"bottomExternal": "S",
"type": "default"
}
],
"layouter": "Stack"
}]; }];

14127
dist/sv.js vendored

File diff suppressed because one or more lines are too long

View File

@ -102,6 +102,10 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
node.appendages.forEach(item => { node.appendages.forEach(item => {
item.setSelectedState(false); item.setSelectedState(false);
item.set({
x: item.G6Item.getModel().x,
y: item.G6Item.getModel().y
});
}); });
} }
@ -110,6 +114,15 @@ export function SolveNodeAppendagesDrag(viewContainer: ViewContainer) {
x: item.G6Item.getModel().x, x: item.G6Item.getModel().x,
y: item.G6Item.getModel().y y: item.G6Item.getModel().y
}); });
if(item instanceof SVNode) {
item.appendages.forEach(appendage => {
appendage.set({
x: appendage.G6Item.getModel().x,
y: appendage.G6Item.getModel().y
});
});
}
}); });
}); });
} }

View File

@ -47,6 +47,28 @@ export const Util = {
return res; return res;
}, },
/**
*
* @param list
* @param category
* @returns
*/
groupBy<T>(list: T[], category: string): { [key: string]: T[] } {
const result = {} as { [key: string]: T[] };
list.forEach(item => {
let value = item[category];
if(result[value] === undefined) {
result[value] = [];
}
result[value].push(item);
});
return result;
},
/** /**
* *
* @param assertFn * @param assertFn

View File

@ -129,7 +129,7 @@ export class SVModel {
} }
}; };
this.G6ModelProps = merge(this.G6ModelProps, newG6ModelProps); this.G6ModelProps = merge.recursive(this.G6ModelProps, newG6ModelProps);
if (this.G6Item) { if (this.G6Item) {
this.g6Instance.updateItem(this.G6Item, this.G6ModelProps); this.g6Instance.updateItem(this.G6Item, this.G6ModelProps);

View File

@ -177,6 +177,6 @@ export class SVMarker extends SVNodeAppendage {
public getLabelSizeRadius(): number { public getLabelSizeRadius(): number {
const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox(); const { width, height } = this.shadowG6Item.getContainer().getChildren()[2].getBBox();
return width > height ? width : height; return Math.max(width, height);
} }
}; };

View File

@ -217,10 +217,10 @@ export class ModelConstructor {
value = node[name]; value = node[name];
// 若没有指针字段的结点则跳过 // 若没有指针字段的结点则跳过
if (!value) continue; if (value === undefined || value === null) continue;
let id = `${group}.${name}#${value}`, let id = `${group}.${name}#${value}`,
indexLabel = new SVIndexLabel(id, name, group, layout, value, node, indexLabelOptions[name]); indexLabel = new SVIndexLabel(id, name, group, layout, value.toString(), node, indexLabelOptions[name]);
indexLabelList.push(indexLabel); indexLabelList.push(indexLabel);
} }

View File

@ -20,7 +20,7 @@ export default Util.registerShape('cursor', {
x: 0, x: 0,
y: 0, y: 0,
text: cfg.label, text: cfg.label,
fill: '#fafafa', fill: 'transparent',
radius: 2, radius: 2,
}, },
name: 'bgRect' name: 'bgRect'

View File

@ -64,7 +64,8 @@ export default Util.registerShape('link-list-node', {
[1, 0.5], [1, 0.5],
[5 / 6, 1], [5 / 6, 1],
[0.5, 1], [0.5, 1],
[0, 0.5] [0, 0.5],
[1 / 3, 1]
]; ];
} }
}, 'rect'); }, 'rect');

View File

@ -14,6 +14,7 @@ import { EngineOptions, LayoutCreator } from "./options";
import { SourceNode } from "./sources"; import { SourceNode } from "./sources";
import { Util } from "./Common/util"; import { Util } from "./Common/util";
import { SVModel } from "./Model/SVModel"; import { SVModel } from "./Model/SVModel";
import { SVNode } from "./Model/SVNode";
@ -72,7 +73,7 @@ SV.registerLayout = function(name: string, layoutCreator: LayoutCreator) {
} }
if(typeof layoutCreator.defineLeakRule !== 'function') { if(typeof layoutCreator.defineLeakRule !== 'function') {
layoutCreator.defineLeakRule = function(models: SVModel[]): SVModel[] { layoutCreator.defineLeakRule = function(models: SVNode[]): SVNode[] {
return models; return models;
} }
} }

View File

@ -146,7 +146,7 @@ export class LayoutProvider {
}, },
left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => { left: (nodeBound: BoundingRect, labelBound: BoundingRect, offset: number) => {
return { return {
x: nodeBound.x - labelBound.width - 2 * offset, x: nodeBound.x - labelBound.width - offset,
y: nodeBound.y + nodeBound.height / 2 y: nodeBound.y + nodeBound.height / 2
}; };
} }
@ -227,8 +227,11 @@ export class LayoutProvider {
containerHeight = this.viewContainer.getG6Instance().getHeight(), containerHeight = this.viewContainer.getG6Instance().getHeight(),
leakAreaHeight = this.engine.viewOptions.leakAreaHeight, leakAreaHeight = this.engine.viewOptions.leakAreaHeight,
leakAreaY = containerHeight - leakAreaHeight, leakAreaY = containerHeight - leakAreaHeight,
xOffset = 50; xOffset = 60;
let prevBound: BoundingRect;
// 避免在泄漏前拖拽节点导致的位置变化,先把节点位置重置为布局后的标准位置
leakModels.forEach(item => { leakModels.forEach(item => {
item.set({ item.set({
x: item.layoutX, x: item.layoutX,
@ -236,17 +239,26 @@ export class LayoutProvider {
}); });
}); });
group.add(...leakModels); const globalLeakGroupBound: BoundingRect = accumulateLeakModels.length ?
const currentLeakGroupBound: BoundingRect = group.getBound(),
globalLeakGroupBound: BoundingRect = accumulateLeakModels.length ?
Bound.union(...accumulateLeakModels.map(item => item.getBound())) : Bound.union(...accumulateLeakModels.map(item => item.getBound())) :
{ x: 0, y: leakAreaY, width: 0, height: 0 }; { x: 0, y: leakAreaY, width: 0, height: 0 };
const { x: groupX, y: groupY } = currentLeakGroupBound, const layoutGroups = Util.groupBy(leakModels, 'group');
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + xOffset - groupX, Object.keys(layoutGroups).forEach(key => {
group.add(...layoutGroups[key]);
const currentBound: BoundingRect = group.getBound(),
prevBoundEnd = prevBound? prevBound.x + prevBound.width: 0,
{ x: groupX, y: groupY } = currentBound,
dx = globalLeakGroupBound.x + globalLeakGroupBound.width + prevBoundEnd + xOffset - groupX,
dy = globalLeakGroupBound.y - groupY; dy = globalLeakGroupBound.y - groupY;
group.translate(dx, dy); group.translate(dx, dy);
group.clear();
Bound.translate(currentBound, dx, dy);
prevBound = currentBound;
});
} }
/** /**

View File

@ -1,6 +1,7 @@
import { EventBus } from "../Common/eventBus"; import { EventBus } from "../Common/eventBus";
import { Util } from "../Common/util"; import { Util } from "../Common/util";
import { Engine } from "../engine"; import { Engine } from "../engine";
import { LayoutGroupTable } from "../Model/modelConstructor";
import { SVLink } from "../Model/SVLink"; import { SVLink } from "../Model/SVLink";
import { SVModel } from "../Model/SVModel"; import { SVModel } from "../Model/SVModel";
import { SVNode } from "../Model/SVNode"; import { SVNode } from "../Model/SVNode";
@ -66,16 +67,31 @@ export class Reconcile {
/** /**
* *
* @param layoutGroupTable
* @param prevModelList * @param prevModelList
* @param modelList * @param modelList
* @returns * @returns
*/ */
private getLeakModels(prevModelList: SVModel[], modelList: SVModel[]): SVModel[] { private getLeakModels(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[]): SVModel[] {
const potentialLeakModels: SVModel[] = prevModelList.filter(item => let potentialLeakModels: SVModel[] = prevModelList.filter(item =>
!modelList.find(model => model.id === item.id) && !item.freed !modelList.find(model => model.id === item.id) && !item.freed
); );
const leakModels: SVModel[] = []; const leakModels: SVModel[] = [];
// 先把节点拿出来
const potentialLeakNodes = potentialLeakModels.filter(item => item.isNode()) as SVNode[],
groups = Util.groupBy<SVNode>(potentialLeakNodes, 'group');
// 再把非节点的model拿出来
potentialLeakModels = potentialLeakModels.filter(item => item.isNode() === false);
Object.keys(groups).forEach(key => {
const leakRule = layoutGroupTable.get(key).layoutCreator.defineLeakRule;
if(leakRule && typeof leakRule === 'function') {
potentialLeakModels.push(...leakRule(groups[key]));
}
});
potentialLeakModels.forEach(item => { potentialLeakModels.forEach(item => {
if (item instanceof SVNode) { if (item instanceof SVNode) {
item.leaked = true; item.leaked = true;
@ -345,14 +361,15 @@ export class Reconcile {
/** /**
* diff * diff
* @param prevLayoutGroupTable
* @param layoutGroupTable * @param layoutGroupTable
* @param prevModelList
* @param modelList
* @param accumulateLeakModels * @param accumulateLeakModels
* @returns * @returns
*/ */
public diff(prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): DiffResult { public diff(layoutGroupTable: LayoutGroupTable, prevModelList: SVModel[], modelList: SVModel[], accumulateLeakModels: SVModel[]): DiffResult {
const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList); const continuousModels: SVModel[] = this.getContinuousModels(prevModelList, modelList);
const leakModels: SVModel[] = this.getLeakModels(prevModelList, modelList); const leakModels: SVModel[] = this.getLeakModels(layoutGroupTable, prevModelList, modelList);
const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels); const appendModels: SVModel[] = this.getAppendModels(prevModelList, modelList, accumulateLeakModels);
const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList); const removeModels: SVModel[] = this.getRemoveModels(prevModelList, modelList);
const updateModels: SVModel[] = [ const updateModels: SVModel[] = [

View File

@ -20,7 +20,7 @@ export class ViewContainer {
private reconcile: Reconcile; private reconcile: Reconcile;
public renderer: Renderer; public renderer: Renderer;
private prevLayoutGroupTable: LayoutGroupTable; private layoutGroupTable: LayoutGroupTable;
private prevModelList: SVModel[]; private prevModelList: SVModel[];
private accumulateLeakModels: SVModel[]; private accumulateLeakModels: SVModel[];
@ -37,7 +37,7 @@ export class ViewContainer {
this.layoutProvider = new LayoutProvider(engine, this); this.layoutProvider = new LayoutProvider(engine, this);
this.renderer = new Renderer(engine, DOMContainer, behaviorsModes); this.renderer = new Renderer(engine, DOMContainer, behaviorsModes);
this.reconcile = new Reconcile(engine, this.renderer); this.reconcile = new Reconcile(engine, this.renderer);
this.prevLayoutGroupTable = new Map(); this.layoutGroupTable = new Map();
this.prevModelList = []; this.prevModelList = [];
this.accumulateLeakModels = []; this.accumulateLeakModels = [];
this.hasLeak = false; // 判断是否已经发生过泄漏 this.hasLeak = false; // 判断是否已经发生过泄漏
@ -75,7 +75,7 @@ export class ViewContainer {
g6Instance.translate(-dx, -dy); g6Instance.translate(-dx, -dy);
} }
this.layoutProvider.layoutAll(this.prevLayoutGroupTable, this.accumulateLeakModels, []); this.layoutProvider.layoutAll(this.layoutGroupTable, this.accumulateLeakModels, []);
g6Instance.refresh(); g6Instance.refresh();
} }
@ -99,7 +99,7 @@ export class ViewContainer {
* *
*/ */
getLayoutGroupTable(): LayoutGroupTable { getLayoutGroupTable(): LayoutGroupTable {
return this.prevLayoutGroupTable; return this.layoutGroupTable;
} }
/** /**
@ -142,7 +142,7 @@ export class ViewContainer {
*/ */
render(layoutGroupTable: LayoutGroupTable) { render(layoutGroupTable: LayoutGroupTable) {
const modelList = Util.convertGroupTable2ModelList(layoutGroupTable), const modelList = Util.convertGroupTable2ModelList(layoutGroupTable),
diffResult = this.reconcile.diff(this.prevModelList, modelList, this.accumulateLeakModels), diffResult = this.reconcile.diff(this.layoutGroupTable, this.prevModelList, modelList, this.accumulateLeakModels),
renderModelList = [ renderModelList = [
...modelList, ...modelList,
...diffResult.REMOVE, ...diffResult.REMOVE,
@ -176,7 +176,7 @@ export class ViewContainer {
this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积 this.accumulateLeakModels.push(...diffResult.LEAKED); // 对泄漏节点进行累积
this.prevLayoutGroupTable = layoutGroupTable; this.layoutGroupTable = layoutGroupTable;
this.prevModelList = modelList; this.prevModelList = modelList;
} }
@ -187,9 +187,10 @@ export class ViewContainer {
this.renderer.destroy(); this.renderer.destroy();
this.reconcile.destroy(); this.reconcile.destroy();
this.layoutProvider = null; this.layoutProvider = null;
this.prevLayoutGroupTable = null; this.layoutGroupTable = null;
this.prevModelList.length = 0; this.prevModelList.length = 0;
this.accumulateLeakModels.length = 0; this.accumulateLeakModels.length = 0;
this.brushSelectedModels.length = 0;
} }

View File

@ -129,7 +129,7 @@ export interface EngineOptions {
export interface LayoutCreator { export interface LayoutCreator {
defineOptions(sourceData: SourceNode[]): LayoutGroupOptions; defineOptions(sourceData: SourceNode[]): LayoutGroupOptions;
sourcesPreprocess?(sourceData: SourceNode[], options: LayoutGroupOptions): SourceNode[]; sourcesPreprocess?(sourceData: SourceNode[], options: LayoutGroupOptions): SourceNode[];
defineLeakRule?(models: SVModel[]): SVModel[]; defineLeakRule?(models: SVNode[]): SVNode[];
layout(nodes: SVNode[], layoutOptions: LayoutOptions); layout(nodes: SVNode[], layoutOptions: LayoutOptions);
[key: string]: any; [key: string]: any;
} }