bug修复

This commit is contained in:
黎智洲 2021-04-20 19:12:41 +08:00
parent 0dcad0f117
commit a623ae7445
20 changed files with 492 additions and 107 deletions

View File

@ -17,6 +17,14 @@ class Arrays extends Engine {
}
}
},
pointer: {
external: {
offset: 8,
style: {
fill: '#f08a5d'
}
}
},
interaction: {
dragNode: false
}
@ -41,7 +49,7 @@ const A = function(container) {
return{
engine: new Arrays(container),
data: [[
{ id: 1 },
{ id: 1, external: 'A' },
{ id: 2 },
{ id: 3 },
{ id: 4 },
@ -56,7 +64,7 @@ const A = function(container) {
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 6 },
{ id: 6, external: 'A' },
{ id: 7 },
{ id: 8 }
]]

View File

@ -164,27 +164,66 @@ class BinaryTree extends Engine {
const BTree = function(container) {
return{
engine: new BinaryTree(container),
data: [[
{ id: 1, child: [2, 3], root: true, external: ['treeA', 'gear'] },
{ id: 2, child: [null, 6] },
{ id: 3, child: [5, 4] },
{ id: 4, external: 'foo' },
{ id: 5 },
{ id: 6, external: 'bar', child: [null, 7] },
{ id: 7 },
{ id: 8, child: [9, 10], root: true },
{ id: 9, child: [11, null] },
{ id: 10 },
{ id: 11 }
],
[
{ id: 1, child: [2, 3], root: true, external: 'treeA' },
{ id: 2, external: 'gear' },
{ id: 3, child: [5, 4] },
{ id: 4, external: 'foo' },
{ id: 5, child: [12, 13] },
{ id: 12 }, { id: 13 }
]]
data: [
// [
// { id: 1, child: [2, 3], root: true, external: ['treeA', 'gear'] },
// { id: 2, child: [null, 6] },
// { id: 3, child: [5, 4] },
// { id: 4, external: 'foo' },
// { id: 5 },
// { id: 6, external: 'bar', child: [null, 7] },
// { id: 7 },
// { id: 8, child: [9, 10], root: true },
// { id: 9, child: [11, null] },
// { id: 10 },
// { id: 11 }
// ],
// [
// { id: 1, child: [2, 3], root: true, external: 'treeA' },
// { id: 2, external: 'gear' },
// { id: 3, child: [5, 4] },
// { id: 4, external: 'foo' },
// { id: 5, child: [12, 13] },
// { id: 12 }, { id: 13 }
// ]
[
{
"external": [
"r",
"T1"
],
"child": [
6385376,
6385424
],
"id": 6385328,
"name": "T1",
"data": "Z",
"root": true
},
{
"child": [
0,
0
],
"id": 6385376,
"name": "T1.lchild",
"data": "A"
},
{
"external": [
"t"
],
"child": [
0,
0
],
"id": 6385424,
"name": "T1.rchild",
"data": "B"
}
]
]
}
};

View File

@ -0,0 +1,162 @@
/**
* 连地址哈希表
*/
class ChainHashTable extends Engine {
defineOptions() {
return {
element: {
head: {
type: 'two-cell-node',
label: '[id]',
size: [70, 40],
style: {
stroke: '#333',
fill: '#b83b5e'
}
},
node: {
type: 'link-list-node',
label: '[id]',
size: [60, 30],
style: {
stroke: '#333',
fill: '#b83b5e'
}
}
},
link: {
start: {
type: 'line',
sourceAnchor: 1,
targetAnchor: 0,
style: {
stroke: '#333',
endArrow: {
path: G6.Arrow.triangle(8, 6, 0),
fill: '#333'
},
startArrow: {
path: G6.Arrow.circle(2, -1),
fill: '#333'
}
}
},
next: {
type: 'line',
sourceAnchor: 1,
targetAnchor: 0,
style: {
stroke: '#333',
endArrow: {
path: G6.Arrow.triangle(8, 6, 0),
fill: '#333'
},
startArrow: {
path: G6.Arrow.circle(2, -1),
fill: '#333'
}
}
}
},
pointer: {
external: {
offset: 8,
style: {
fill: '#f08a5d'
}
}
},
layout: {
xInterval: 50,
yInterval: 50
},
interaction: {
dragNode: ['node']
}
};
}
/**
* 对子树进行递归布局
* @param node
* @param parent
*/
layoutItem(node, prev, layoutOptions) {
if(!node) {
return null;
}
let width = node.get('size')[0];
if(prev) {
node.set('y', prev.get('y'));
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
}
if(node.next) {
this.layoutItem(node.next, node, layoutOptions);
}
}
layout(elements, layoutOptions) {
let headNode = elements.head;
for(let i = 0; i < headNode.length; i++) {
let node = headNode[i],
height = node.get('size')[1];
node.set('y', node.get('y') + i * height);
if(node.start) {
let y = node.get('y') + height - node.start.get('size')[1],
x = layoutOptions.xInterval * 2.5;
node.start.set({ x, y });
this.layoutItem(node.start, null, layoutOptions);
}
}
}
}
const CHT = function(container) {
return{
engine: new ChainHashTable(container),
data: [
{
head: [{
id: 0,
start: 'node#0'
}, {
id: 2,
start: 'node#2'
}],
node: [{
id: 0,
next: 1
}, {
id: 1
},{
id: 2,
next: 3
}, {
id: 3
}]
},
{
}
]
}
};

View File

@ -1,26 +1,13 @@
/**
* 单链表
*/
class HashLinkList extends Engine {
class LinkStack extends Engine {
defineOptions() {
return {
element: {
headNode: {
type: 'link-list-node',
label: '[id]',
size: [60, 30],
style: {
stroke: '#333',
fill: '#b83b5e'
}
},
node: {
default: {
type: 'link-list-node',
label: '[id]',
size: [60, 30],
@ -34,7 +21,7 @@
next: {
type: 'line',
sourceAnchor: 1,
targetAnchor: 0,
targetAnchor: 2,
style: {
stroke: '#333',
endArrow: {
@ -50,7 +37,7 @@
},
pointer: {
external: {
offset: 14,
offset: 8,
style: {
fill: '#f08a5d'
}
@ -58,7 +45,7 @@
},
layout: {
xInterval: 50,
yInterval: 50
yInterval: 30
}
};
}
@ -74,11 +61,11 @@
return null;
}
let width = node.get('size')[0];
let height = node.get('size')[1];
if(prev) {
node.set('y', prev.get('y'));
node.set('x', prev.get('x') + layoutOptions.xInterval + width);
node.set('x', prev.get('x'));
node.set('y', prev.get('y') + layoutOptions.yInterval + height);
}
if(node.next) {
@ -103,20 +90,20 @@
for(i = 0; i < rootNodes.length; i++) {
let root = rootNodes[i],
height = root.get('size')[1];
width = root.get('size')[0];
root.set('y', root.get('y') + i * (layoutOptions.yInterval + height));
root.set('x', root.get('x') + i * (layoutOptions.xInterval + width));
this.layoutItem(root, null, layoutOptions);
}
}
}
const LList = function(container) {
const LStack = function(container) {
return{
engine: new LinkList(container),
engine: new LinkStack(container),
data: [[
{ id: 1, root: true, next: 2, external: ['gg'] },
{ id: 1, root: true, next: 2 },
{ id: 2, next: 3 },
{ id: 3, next: 4 },
{ id: 4, next: 5 },
@ -128,7 +115,7 @@ const LList = function(container) {
{ id: 10 }
],
[
{ id: 1, root: true, next: 2, external: ['gg'] },
{ id: 1, root: true, next: 2 },
{ id: 2, next: 3 },
{ id: 3, next: 6 },
{ id: 6, next: 7 },

71
demo/dataStruct/Stack.js Normal file
View File

@ -0,0 +1,71 @@
class Stack extends Engine {
defineOptions() {
return {
element: {
default: {
type: 'rect',
label: '[id]',
size: [60, 30],
style: {
stroke: '#333',
fill: '#95e1d3'
}
}
},
pointer: {
external: {
offset: 8,
style: {
fill: '#f08a5d'
}
}
},
interaction: {
dragNode: false
}
};
}
layout(elements, layoutOptions) {
let blocks = elements.default;
for(let i = 1; i < blocks.length; i++) {
let item = blocks[i],
prev = blocks[i - 1],
height = item.get('size')[1];
item.set('y', prev.get('y') + height);
}
}
}
const St = function(container) {
return{
engine: new Stack(container),
data: [[
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
{ id: 8 },
{ id: 9 },
{ id: 10 }
],
[
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 6 },
{ id: 7 },
{ id: 8 }
]]
}
};

View File

@ -37,7 +37,7 @@ class LinkList extends Engine {
},
pointer: {
external: {
offset: 14,
offset: 8,
style: {
fill: '#f08a5d'
}

View File

@ -39,13 +39,16 @@ const Engine = SV.Engine,
</script>
<script src="./dataStruct/BinaryTree.js"></script>
<script src="./dataStruct/linkList.js"></script>
<script src="./dataStruct/LinkList.js"></script>
<script src="./dataStruct/Array.js"></script>
<script src="./dataStruct/ChainHashTable.js"></script>
<script src="./dataStruct/Stack.js"></script>
<script src="./dataStruct/LinkStack.js"></script>
<script>
const engines = [BTree, LList, A];
const engines = [BTree, LList, A, CHT, St, LStack];
let cur = engines[2](document.getElementById('container'));
let cur = engines[5](document.getElementById('container'));
cur.engine.render(cur.data[0]);

2
dist/sv.js vendored

File diff suppressed because one or more lines are too long

View File

@ -158,7 +158,7 @@ export class ModelConstructor {
// 若没有指针字段的结点则跳过
if(!pointerData) continue;
let id = name + '#' + (Array.isArray(pointerData)? pointerData.join('-'): pointerData),
let id = name + '.' + (Array.isArray(pointerData)? pointerData.join('-'): pointerData),
pointer = this.createPointer(id, name, pointerData, element);
pointerContainer[name].push(pointer);
@ -176,9 +176,14 @@ export class ModelConstructor {
private createElement(sourceElement: SourceElement, elementName: string): Element {
let elementOption = this.engine.elementOptions[elementName],
element: Element = undefined,
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '';
label = elementOption.label? this.parserElementContent(sourceElement, elementOption.label): '',
id = elementName + '.' + sourceElement.id.toString();
element = new Element(elementName, sourceElement);
if(label === null || label === undefined) {
label = '';
}
element = new Element(id, elementName, sourceElement);
element.initProps(elementOption);
element.set('label', label);
element.sourceElement = sourceElement;
@ -251,7 +256,7 @@ export class ModelConstructor {
element: Element,
linkTarget: LinkTarget
): Element {
let elementName = element.name,
let elementName = element.modelName,
elementList: Element[],
targetId = linkTarget,
targetElement = null;
@ -277,7 +282,7 @@ export class ModelConstructor {
return null;
}
targetElement = elementList.find(item => item.id === targetId);
targetElement = elementList.find(item => item.sourceId === targetId);
return targetElement || null;
}
};

View File

@ -17,6 +17,7 @@ export interface G6NodeModel {
labelCfg: ElementLabelOption;
externalPointerId: string;
modelType: string;
modelName: string;
};
@ -35,8 +36,8 @@ export interface G6EdgeModel {
export class Model {
id: string;
name: string;
type: string;
modelName: string;
modelType: string;
props: G6NodeModel | G6EdgeModel;
shadowG6Item;
@ -45,7 +46,7 @@ export class Model {
constructor(id: string, name: string) {
this.id = id;
this.name = name;
this.modelName = name;
this.shadowG6Item = null;
this.renderG6Item = null;
this.G6Item = null;
@ -148,17 +149,20 @@ export class Model {
export class Element extends Model {
type = 'element';
modelType = 'element';
sourceElement: SourceElement;
sourceId: string;
constructor(type: string, sourceElement: SourceElement) {
super(sourceElement.id.toString(), type);
constructor(id: string, type: string, sourceElement: SourceElement) {
super(id, type);
Object.keys(sourceElement).map(prop => {
if(prop !== 'id') {
this[prop] = sourceElement[prop];
}
});
this.sourceId = this.id.split('.')[1];
}
protected defineProps(option: ElementOption) {
@ -174,7 +178,8 @@ export class Element extends Model {
style: Util.objectClone<Style>(option.style),
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
externalPointerId: null,
modelType: this.type
modelType: this.modelType,
modelName: this.modelName
};
}
};
@ -182,7 +187,7 @@ export class Element extends Model {
export class Link extends Model {
type = 'link';
modelType = 'link';
element: Element;
target: Element;
index: number;
@ -216,7 +221,9 @@ export class Link extends Model {
targetAnchor,
label: option.label,
style: Util.objectClone<Style>(option.style),
labelCfg: Util.objectClone<LinkLabelOption>(option.labelOptions)
labelCfg: Util.objectClone<LinkLabelOption>(option.labelOptions),
modelType: this.modelType,
modelName: this.modelName
};
}
};
@ -224,7 +231,7 @@ export class Link extends Model {
export class Pointer extends Model {
type = 'pointer';
modelType = 'pointer';
target: Element;
label: string | string[];
@ -250,7 +257,8 @@ export class Pointer extends Model {
style: Util.objectClone<Style>(option.style),
labelCfg: Util.objectClone<ElementLabelOption>(option.labelOptions),
externalPointerId: null,
modelType: this.type
modelType: this.modelType,
modelName: this.modelName
};
}
};

View File

@ -56,7 +56,9 @@ export default G6.registerNode('link-list-node', {
getAnchorPoints() {
return [
[0, 0.5],
[5 / 6, 0.5]
[5 / 6, 0.5],
[5 / 6, 0],
[5 / 6, 1]
];
}
});

View File

@ -0,0 +1,63 @@
import * as G6 from "./../Lib/g6.js";
export default G6.registerNode('two-cell-node', {
draw(cfg, group) {
cfg.size = cfg.size || [30, 10];
const width = cfg.size[0],
height = cfg.size[1];
const wrapperRect = group.addShape('rect', {
attrs: {
x: width / 2,
y: height / 2,
width: width,
height: height,
stroke: cfg.style.stroke,
fill: 'transparent'
},
name: 'wrapper'
});
group.addShape('rect', {
attrs: {
x: width / 2,
y: height / 2,
width: width / 2,
height: height,
fill: cfg.style.fill,
stroke: cfg.style.stroke
},
name: 'left-rect',
draggable: true
});
if (cfg.label) {
const style = (cfg.labelCfg && cfg.labelCfg.style) || {};
group.addShape('text', {
attrs: {
x: width * (3 / 4),
y: height,
textAlign: 'center',
textBaseline: 'middle',
text: cfg.label,
fill: style.fill || '#000',
fontSize: style.fontSize || 16
},
name: 'text',
draggable: true
});
}
return wrapperRect;
},
getAnchorPoints() {
return [
[0, 0.5],
[3 / 4, 0.5]
];
}
});

View File

@ -5,6 +5,7 @@ import externalPointer from "./RegisteredShape/externalPointer";
import * as G6 from "./Lib/g6.js";
import linkListNode from "./RegisteredShape/linkListNode";
import binaryTreeNode from "./RegisteredShape/binaryTreeNode";
import twoCellNode from "./RegisteredShape/twoCellNode";
export const SV = {
@ -13,7 +14,7 @@ export const SV = {
Bound: Bound,
G6,
registeredShape: [
externalPointer, linkListNode, binaryTreeNode
externalPointer, linkListNode, binaryTreeNode, twoCellNode
]
};

View File

@ -9,14 +9,15 @@ export class Behavior {
this.engine = engine;
this.graphInstance = graphInstance;
const interactionOptions = this.engine.interactionOptions;
const interactionOptions = this.engine.interactionOptions,
selectNode: boolean | string[] = interactionOptions.selectNode;
if(interactionOptions.dragNode) {
this.initDragNode();
}
if(interactionOptions.selectNode) {
this.initSelectNode();
this.initSelectNode(selectNode);
}
}
@ -67,16 +68,27 @@ export class Behavior {
/**
* /
* @param selectNode
*/
private initSelectNode() {
private initSelectNode(selectNode: boolean | string[]) {
let defaultHighlightColor = '#f08a5d',
curSelectItem = null,
curSelectItemStyle = null;
if(selectNode === false) {
return;
}
const selectCallback = ev => {
const item = ev.item,
model = item.getModel(),
type = item.getType(),
highlightColor = item.getModel().style.selectedColor;
name = model.modelName,
highlightColor = model.style.selectedColor;
if(Array.isArray(selectNode) && selectNode.find(item => item === name) === undefined) {
return;
}
if(curSelectItem && curSelectItem !== item) {
curSelectItem.update({

View File

@ -1,4 +1,3 @@
import { Util } from "../Common/util";
import { Engine } from "../engine";
import { ConstructedData } from "../Model/modelConstructor";
import { Element, Model, Pointer } from "../Model/modelData";
@ -71,7 +70,7 @@ export class Layouter {
modelList.forEach(item => {
item.G6Item = item.shadowG6Item;
if(item.type === 'element' || item.type === 'pointer') {
if(item.modelType === 'element' || item.modelType === 'pointer') {
item.set('rotation', item.get('rotation'));
item.set({ x: 0, y: 0 });
}

View File

@ -37,28 +37,7 @@ export class Renderer {
const enable: boolean = this.engine.animationOptions.enable === undefined? true: this.engine.animationOptions.enable,
duration: number = this.engine.animationOptions.duration,
timingFunction: string = this.engine.animationOptions.timingFunction,
interactionOptions = this.engine.interactionOptions;
const modeMap = {
drag: 'drag-canvas',
zoom: 'zoom-canvas',
dragNode: {
type: 'drag-node',
shouldBegin: n => {
// 不允许拖拽外部指针
if (n.item && n.item.getModel().modelType === 'pointer') return false;
return true;
}
}
},
defaultModes = [];
Object.keys(interactionOptions).forEach(item => {
if(interactionOptions[item] === true && modeMap[item] !== undefined) {
defaultModes.push(modeMap[item]);
}
});
timingFunction: string = this.engine.animationOptions.timingFunction;
this.graphInstance = new SV.G6.Graph({
container: DOMContainer,
@ -71,7 +50,7 @@ export class Renderer {
},
fitView: this.engine.layoutOptions.fitView,
modes: {
default: defaultModes
default: this.initBehaviors()
}
});
@ -82,6 +61,54 @@ export class Renderer {
this.animations = new Animations(duration, timingFunction);
}
/**
*
* @returns
*/
private initBehaviors() {
const interactionOptions = this.engine.interactionOptions,
dragNode: boolean | string[] = interactionOptions.dragNode,
dragNodeFilter = node => {
let model = node.item.getModel();
if(node.item === null) {
return false;
}
if(model.modelType === 'pointer') {
return false;
}
if(typeof dragNode === 'boolean') {
return dragNode;
}
if(Array.isArray(dragNode) && dragNode.indexOf(model.modelName) > -1) {
return true;
}
return false;
}
const modeMap = {
drag: 'drag-canvas',
zoom: 'zoom-canvas',
dragNode: {
type: 'drag-node',
shouldBegin: node => dragNodeFilter(node)
}
},
defaultModes = [];
Object.keys(interactionOptions).forEach(item => {
if(interactionOptions[item] && modeMap[item] !== undefined) {
defaultModes.push(modeMap[item]);
}
});
return defaultModes;
}
/**
* G6Data
* @param prevData

View File

@ -102,7 +102,7 @@ export class Engine {
this.layouter.layout(this.constructedData, modelList, this.layout);
modelList.forEach(item => {
if(item.type === 'link') return;
if(item.modelType === 'link') return;
let model = item.G6Item.getModel(),
x = item.get('x'),

View File

@ -74,8 +74,8 @@ export interface AnimationOptions {
export interface InteractionOptions {
drag: boolean;
zoom: boolean;
dragNode: boolean;
selectNode: boolean;
dragNode: boolean | string[];
selectNode: boolean | string[];
};

View File

@ -5,11 +5,9 @@ module.exports = {
entry: './src/StructV.ts',
output: {
filename: './sv.js',
//path: path.resolve(__dirname, './../Visualizer/src/StructV'),
libraryTarget: 'umd'
},
resolve: {
// 先尝试以ts为后缀的TypeScript源码文件
extensions: ['.ts', '.js']
},
module: {