Initial commit
This commit is contained in:
commit
6c7b0d8a07
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
14
atlconfig.json
Normal file
14
atlconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"module": "commonJS",
|
||||
"removeComments": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
1
dist/sv.js
vendored
Normal file
1
dist/sv.js
vendored
Normal file
File diff suppressed because one or more lines are too long
14
package.json
Normal file
14
package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"typescript": "^3.2.2",
|
||||
"webpack": "^4.28.2",
|
||||
"zrender": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-cli": "^3.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && webpack"
|
||||
}
|
||||
}
|
116
src/Common/util.ts
Normal file
116
src/Common/util.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import * as zrender from "zrender";
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 工具函数
|
||||
*/
|
||||
export const Util = {
|
||||
|
||||
/**
|
||||
* 生成唯一id
|
||||
*/
|
||||
generateId(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 扩展对象
|
||||
* @param origin 原对象
|
||||
* @param ext 扩展的对象
|
||||
*/
|
||||
extends(origin, ext) {
|
||||
zrender.util.extend(origin, ext);
|
||||
},
|
||||
|
||||
/**
|
||||
* 合并对象
|
||||
* @param origin
|
||||
* @param dest
|
||||
*/
|
||||
merge(origin, dest) {
|
||||
zrender.util.merge(origin, dest, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* 从列表中移除元素
|
||||
* @param list 移除列表
|
||||
* @param fn 移除判断规则
|
||||
*/
|
||||
removeFromList<T>(list: T[], fn: (item: T) => boolean) {
|
||||
for(let i = 0; i < list.length; i++) {
|
||||
fn(list[i]) && list.splice(i, 1) && i--;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从一个由数组组成的路径中获取几何中心
|
||||
* @param path
|
||||
*/
|
||||
getPathCenter(path: Array<[number, number]>): [number, number] {
|
||||
let maxX = -Infinity,
|
||||
minX = Infinity,
|
||||
maxY = -Infinity,
|
||||
minY = Infinity;
|
||||
|
||||
path.map(item => {
|
||||
if(item[0] > maxX) maxX = item[0];
|
||||
if(item[0] < minX) minX = item[0];
|
||||
if(item[1] > maxY) maxY = item[1];
|
||||
if(item[1] < minY) minY = item[1];
|
||||
});
|
||||
|
||||
return [(maxX + minX) / 2, (maxY + minY) / 2];
|
||||
},
|
||||
|
||||
/**
|
||||
* 断言函数
|
||||
* @param assertFn
|
||||
* @param errorText
|
||||
*/
|
||||
assert(condition: boolean, errorText: string): void | never {
|
||||
if(condition) {
|
||||
throw errorText;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取类的名称
|
||||
* @param classConstructor
|
||||
*/
|
||||
getClassName(classConstructor): string {
|
||||
return classConstructor.prototype.constructor.toString().split(' ')[1];
|
||||
},
|
||||
|
||||
/**
|
||||
* 文本解析
|
||||
* @param text
|
||||
*/
|
||||
textParser(text: string): string[] | string {
|
||||
let fieldReg = /\[[^\]]*\]/g;
|
||||
|
||||
if(fieldReg.test(text)) {
|
||||
let contents = text.match(fieldReg),
|
||||
values = contents.map(item => item.replace(/\[|\]/g, ''));
|
||||
return values;
|
||||
}
|
||||
else {
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 牵制某个值
|
||||
* @param value
|
||||
*/
|
||||
clamp(value: number, max: number, min: number): number {
|
||||
if(value <= max && value >= min) return value;
|
||||
if(value > max) return max;
|
||||
if(value < min) return min;
|
||||
}
|
||||
};
|
||||
|
219
src/Common/vector.ts
Normal file
219
src/Common/vector.ts
Normal file
@ -0,0 +1,219 @@
|
||||
|
||||
|
||||
// 二维向量 {x, y}
|
||||
export class Vector {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(x?: number, y?: number) {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
|
||||
if(x !== undefined && y !== undefined) {
|
||||
this.set(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
//-------------操作----------------
|
||||
|
||||
/**
|
||||
* 设置值
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
set(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相加
|
||||
* @param v
|
||||
*/
|
||||
add(v: Vector, out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
|
||||
out.x = this.x + v.x;
|
||||
out.y = this.y + v.y;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 相减
|
||||
* @param v
|
||||
*/
|
||||
sub(v: Vector, out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
|
||||
out.x = this.x - v.x;
|
||||
out.y = this.y - v.y;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 点积
|
||||
* @param v
|
||||
*/
|
||||
dot(v: Vector): number {
|
||||
return this.x * v.x + this.y * v.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 叉积
|
||||
* @param v
|
||||
*/
|
||||
cro(v: Vector): number {
|
||||
return this.x * v.y - v.x * this.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 与标量进行叉积
|
||||
* @param n
|
||||
*/
|
||||
croNum(n: number, out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
|
||||
out.x = -n * this.y;
|
||||
out.y = n * this.x;
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 投影
|
||||
* @param v
|
||||
*/
|
||||
pro(v: Vector): number {
|
||||
return this.dot(v) / v.len();
|
||||
}
|
||||
|
||||
/**
|
||||
* 法向
|
||||
*/
|
||||
nor(out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
|
||||
out.x = this.y;
|
||||
out.y = -this.x;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求模
|
||||
*/
|
||||
len(): number {
|
||||
return Math.hypot(this.x, this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 平方模(节省求平方根操作)
|
||||
*/
|
||||
len_s(): number {
|
||||
return this.x * this.x + this.y * this.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单位化
|
||||
*/
|
||||
nol(): Vector {
|
||||
let len = this.len();
|
||||
|
||||
if(len === 0) {
|
||||
return new Vector();
|
||||
}
|
||||
|
||||
this.x = this.x / len;
|
||||
this.y = this.y / len;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放
|
||||
* @param n
|
||||
*/
|
||||
scl(n: number, out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
out.x = n * this.x;
|
||||
out.y = n * this.y;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反向
|
||||
*/
|
||||
inv(out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
out.x = -this.x;
|
||||
out.y = -this.y;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两向量是否相等
|
||||
* @param v
|
||||
*/
|
||||
eql(v: Vector): boolean {
|
||||
return this.x === v.x && this.y === v.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求两向量夹角(弧度制)
|
||||
* @param v
|
||||
*/
|
||||
ang(v: Vector): number {
|
||||
return Math.acos(this.dot(v) / (this.len() * v.len()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆向量
|
||||
*/
|
||||
col(): Vector {
|
||||
return new Vector(this.x, this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绕某点旋转向量
|
||||
* @param radian 角度(弧度制)
|
||||
* @param point 绕的点
|
||||
*/
|
||||
rot(radian: number, point: Vector, out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
|
||||
let cos = Math.cos(radian),
|
||||
sin = Math.sin(radian),
|
||||
dx = this.x - point.x,
|
||||
dy = this.y - point.y;
|
||||
|
||||
out.x = point.x + (dx * cos - dy * sin);
|
||||
out.y = point.y + (dx * sin + dy * cos);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 求一个向量(点)按照direction方向,延长len长度后的坐标
|
||||
* @param direction
|
||||
* @param len
|
||||
*/
|
||||
loc(direction: Vector, len: number, out?: Vector): Vector {
|
||||
out = out || new Vector();
|
||||
|
||||
direction = direction.nol();
|
||||
out.x = this.x + direction.x * len;
|
||||
out.y = this.y + direction.y * len;
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const _tempVector1 = new Vector();
|
||||
export const _tempVector2 = new Vector();
|
||||
export const _tempVector3 = new Vector();
|
||||
export const _tempVector4 = new Vector();
|
||||
|
163
src/Model/element.ts
Normal file
163
src/Model/element.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { SourceElement } from "../sources";
|
||||
import { Shape, ShapeStatus } from "../View/shape";
|
||||
import { zrShape } from "../View/shapeScheduler";
|
||||
import { Link } from "./link";
|
||||
import { Pointer } from "./pointer";
|
||||
|
||||
|
||||
export interface Style {
|
||||
// 填充颜色
|
||||
fill: string;
|
||||
// 图形文本
|
||||
text: string;
|
||||
// 文本颜色
|
||||
textFill: string;
|
||||
// 字体大小
|
||||
fontSize: number;
|
||||
// 字重
|
||||
fontWeight: number;
|
||||
// 描边样式
|
||||
stroke: string;
|
||||
// 透明度
|
||||
opacity: number;
|
||||
// 线宽
|
||||
lineWidth: number;
|
||||
// 其余属性(免得每次新增属性都要声明一个新的子interface)
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ElementStatus {
|
||||
x: number;
|
||||
y: number;
|
||||
rotation: number;
|
||||
width: number;
|
||||
height: number;
|
||||
zIndex: number;
|
||||
content: string;
|
||||
style: Style;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export class Element {
|
||||
id: any;
|
||||
elementId: string = null;
|
||||
elementLabel: string = null;
|
||||
|
||||
elementStatus: ElementStatus = null;
|
||||
zrShape: zrShape[] = [];
|
||||
relativeLinks: Link[] = [];
|
||||
relativePointers: Pointer[] = [];
|
||||
|
||||
isDirty: boolean= false;
|
||||
|
||||
// 给sourceElement的部分
|
||||
[key: string]: any;
|
||||
|
||||
constructor(elementLabel: string, sourceElement: SourceElement) {
|
||||
this.elementLabel = elementLabel;
|
||||
|
||||
Object.keys(sourceElement).map(prop => {
|
||||
this[prop] = sourceElement[prop];
|
||||
});
|
||||
|
||||
this.elementStatus = {
|
||||
x: 0, y: 0,
|
||||
rotation: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
zIndex: 1,
|
||||
content: '',
|
||||
style: {
|
||||
fill: '#000',
|
||||
text: '',
|
||||
textFill: '#000',
|
||||
fontSize: 15,
|
||||
fontWeight: null,
|
||||
stroke: null,
|
||||
opacity: 1,
|
||||
transformText: true,
|
||||
lineWidth: 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* set方法,设置elementStatus的值
|
||||
* @param propName
|
||||
* @param value
|
||||
*/
|
||||
set(propName: string, value: any) {
|
||||
if(this.elementStatus[propName] !== undefined) {
|
||||
this.elementStatus[propName] = value;
|
||||
}
|
||||
|
||||
this.setDirty(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 元素是否为脏
|
||||
* @param isDirty
|
||||
*/
|
||||
setDirty(isDirty: boolean) {
|
||||
this.isDirty = isDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义该element映射的图形
|
||||
* @param shapes
|
||||
* @param elementStatus
|
||||
* @override
|
||||
*/
|
||||
renderShape(shapes: Shape[] | Shape, elementStatus: ElementStatus) { }
|
||||
|
||||
/**
|
||||
* 应用shapeOptions
|
||||
* @param shapeOptions
|
||||
*/
|
||||
applyShapeOptions(shapeOptions: Partial<ShapeStatus>) {
|
||||
Util.extends(this.elementStatus, shapeOptions);
|
||||
}
|
||||
|
||||
// ------------------------------- 钩子方法 ------------------------------
|
||||
|
||||
onClick(event: any) { }
|
||||
|
||||
/**
|
||||
* 当结点连接其他结点触发
|
||||
* @param targetEle
|
||||
*/
|
||||
onLinkTo(targetEle: Element) {};
|
||||
|
||||
/**
|
||||
* 当结点被其他结点连接时触发
|
||||
* @param emitEle
|
||||
*/
|
||||
onLinkFrom(emitEle: Element) {};
|
||||
|
||||
/**
|
||||
* 当结点断开与其他结点触发
|
||||
* @param targetEle
|
||||
*/
|
||||
onUnLinkTo(targetEle: Element) {}
|
||||
|
||||
/**
|
||||
* 当结点被其他结点断开连接时触发
|
||||
* @param emitEle
|
||||
*/
|
||||
onUnLinkFrom(emitEle: Element) {}
|
||||
|
||||
/**
|
||||
* 当指向结点时触发
|
||||
*/
|
||||
onRefer() {}
|
||||
|
||||
/**
|
||||
* 当指针离开该结点触发
|
||||
*/
|
||||
onUnRefer() {}
|
||||
};
|
155
src/Model/elementScheduler.ts
Normal file
155
src/Model/elementScheduler.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { Engine } from "../engine";
|
||||
import { SourceElement, Sources } from "../sources";
|
||||
import { Shape, ShapeStatus } from "../View/shape";
|
||||
import { ZrShapeConstructor } from "../View/shapeScheduler";
|
||||
import { Element } from "./element";
|
||||
|
||||
|
||||
// 元素集类型
|
||||
export type ElementContainer = { [key: string]: Element[] };
|
||||
export type ElementConstructor = { new(elementLabel: string, sourceElement: SourceElement): Element };
|
||||
|
||||
|
||||
export class ElementScheduler {
|
||||
private engine: Engine;
|
||||
// 元素队列
|
||||
private elementList: Element[] = [];
|
||||
// 元素容器,即源数据经element包装后的结构
|
||||
private elementContainer: ElementContainer = {};
|
||||
|
||||
private elementMap: {
|
||||
[key: string]: {
|
||||
elementConstructor: ElementConstructor,
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
}
|
||||
};
|
||||
|
||||
constructor(engine: Engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个元素的信息
|
||||
* @param elementLabel
|
||||
* @param elementConstructor
|
||||
* @param zrShapeConstructors
|
||||
* @param shapeOptions
|
||||
*/
|
||||
setElementMap(
|
||||
elementLabel: string,
|
||||
elementConstructor: ElementConstructor,
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
) {
|
||||
this.elementMap[elementLabel] = {
|
||||
elementConstructor,
|
||||
zrShapeConstructors,
|
||||
shapeOptions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 从源数据构建 element 集
|
||||
* 主要工作:
|
||||
* - 遍历源数据,将每个 SourceElement 转化为 Element
|
||||
* - 处理连接
|
||||
* - 处理指针
|
||||
* @param sourceData
|
||||
*/
|
||||
constructElements(sourceData: Sources) {
|
||||
if(Array.isArray(sourceData)) {
|
||||
this.elementContainer['element'] = [];
|
||||
sourceData.forEach(item => {
|
||||
if(item) {
|
||||
let ele = this.createElement(item, 'element');
|
||||
this.elementContainer['element'].push(ele);
|
||||
this.elementList.push(ele);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
Object.keys(sourceData).forEach(prop => {
|
||||
this.elementContainer[prop] = [];
|
||||
sourceData[prop].forEach(item => {
|
||||
if(item) {
|
||||
let ele = this.createElement(item, prop);
|
||||
this.elementContainer[prop].push(ele);
|
||||
this.elementList.push(ele);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 元素工厂,创建Element
|
||||
* @param sourceElement
|
||||
* @param elementLabel
|
||||
*/
|
||||
private createElement(sourceElement: SourceElement, elementLabel: string): Element {
|
||||
let elementInfo = this.elementMap[elementLabel],
|
||||
shapes: Shape[] | Shape;
|
||||
|
||||
if(elementInfo === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let { elementConstructor, zrShapeConstructors, shapeOptions } = elementInfo,
|
||||
element: Element = null,
|
||||
elementId = `${elementLabel}#${sourceElement.id}`;
|
||||
|
||||
element = new elementConstructor(elementLabel, sourceElement);
|
||||
element.applyShapeOptions(shapeOptions);
|
||||
|
||||
if(Array.isArray(zrShapeConstructors)) {
|
||||
shapes = zrShapeConstructors.map((item, index) => new Shape(`${elementId}(${index})`, item, element));
|
||||
}
|
||||
else {
|
||||
shapes = new Shape(`elementId`, zrShapeConstructors, element);
|
||||
}
|
||||
|
||||
element.defineShape(shapes, element.elementStatus);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
public updateShapes() {
|
||||
for(let i = 0; i < this.elementList.length; i++) {
|
||||
let ele = this.elementList[i];
|
||||
|
||||
if(ele.isDirty) {
|
||||
ele.renderShape();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有element元素
|
||||
*/
|
||||
public getElementContainer(): ElementContainer | Element[] {
|
||||
let keys = Object.keys(this.elementContainer);
|
||||
|
||||
if(keys.length === 1 && keys[0] === 'element') {
|
||||
return this.elementContainer['element'];
|
||||
}
|
||||
|
||||
return this.elementContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取element列表
|
||||
*/
|
||||
public getElementList(): Element[] {
|
||||
return this.elementList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
public reset() {
|
||||
this.elementList.length = 0;
|
||||
this.elementContainer = {};
|
||||
}
|
||||
};
|
43
src/Model/link.ts
Normal file
43
src/Model/link.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { LinkTarget } from "../sources";
|
||||
import { zrShape } from "../View/shapeScheduler";
|
||||
import { Element, Style } from "./element";
|
||||
import { LabelStyle } from "./pointer";
|
||||
|
||||
|
||||
export interface LinkOptions {
|
||||
style: Style;
|
||||
labelStyle: LabelStyle;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class Link {
|
||||
// 连线 id
|
||||
id: string;
|
||||
// 连线起始 element
|
||||
element: Element;
|
||||
// 连线目标 element
|
||||
target: Element;
|
||||
// 连线类型名称
|
||||
linkName: string;
|
||||
// 连线图形实例
|
||||
zrShapes: zrShape[];
|
||||
// 连线序号
|
||||
index: number;
|
||||
// 连线在源数据的声明
|
||||
sourceLinkTarget: LinkTarget;
|
||||
|
||||
isDirty: boolean = false;
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 元素是否为脏
|
||||
* @param isDirty
|
||||
*/
|
||||
setDirty(isDirty: boolean) {
|
||||
this.isDirty = isDirty;
|
||||
}
|
||||
};
|
53
src/Model/linkScheduler.ts
Normal file
53
src/Model/linkScheduler.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { ShapeStatus } from "../View/shape";
|
||||
import { ZrShapeConstructor } from "../View/shapeScheduler";
|
||||
import { Element, Style } from "./element";
|
||||
import { Link } from "./link";
|
||||
|
||||
|
||||
|
||||
export interface LinkOptions {
|
||||
style: Style;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class LinkScheduler {
|
||||
private links: Link[] = [];
|
||||
private prevLinks: Link[] = [];
|
||||
private linkMap: {
|
||||
[key: string]: {
|
||||
linkConstructor: { new(): Link },
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
}
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建链接模型
|
||||
* @param elementList
|
||||
*/
|
||||
constructLinks(elementList: Element[]) {
|
||||
|
||||
}
|
||||
|
||||
setLinkMap(
|
||||
linkLabel: string,
|
||||
linkConstructor: { new(): Link },
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
) {
|
||||
this.linkMap[linkLabel] = {
|
||||
linkConstructor,
|
||||
zrShapeConstructors,
|
||||
shapeOptions
|
||||
};
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.links.length = 0;
|
||||
}
|
||||
}
|
53
src/Model/pointer.ts
Normal file
53
src/Model/pointer.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { zrShape } from "../View/shapeScheduler";
|
||||
import { Style } from "./element";
|
||||
|
||||
|
||||
export interface LabelStyle extends Style {
|
||||
textBackgroundColor: 'rgba(0, 0, 0, 1)',
|
||||
textFill: '#fff',
|
||||
textPadding: [4, 4, 4, 4]
|
||||
};
|
||||
|
||||
|
||||
export interface PointerOptions {
|
||||
labelStyle: LabelStyle;
|
||||
style: Style;
|
||||
};
|
||||
|
||||
|
||||
export class Pointer {
|
||||
// 指针 id
|
||||
id: string;
|
||||
// 指针图形实例
|
||||
zrShapes: zrShape[];
|
||||
// 指针类型名称
|
||||
pointerLabel: string;
|
||||
// 被该指针合并的其他指针
|
||||
branchPointer: Pointer[];
|
||||
// 若该指针是一个被合并的指针,保存合并这个指针的主指针
|
||||
masterPointer: Pointer;
|
||||
|
||||
// 指针标签内容
|
||||
text: string;
|
||||
// 指针标签图形实例
|
||||
textZrShapes: Text[];
|
||||
// 逗号图形实例
|
||||
commaShapes: Text[];
|
||||
// 目标 element
|
||||
target: Element;
|
||||
|
||||
isDirty: boolean = false;
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 元素是否为脏
|
||||
* @param isDirty
|
||||
*/
|
||||
setDirty(isDirty: boolean) {
|
||||
this.isDirty = isDirty;
|
||||
}
|
||||
};
|
55
src/Model/pointerScheduler.ts
Normal file
55
src/Model/pointerScheduler.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { ShapeStatus } from "../View/shape";
|
||||
import { ZrShapeConstructor } from "../View/shapeScheduler";
|
||||
import { Element, Style } from "./element";
|
||||
import { Pointer } from "./pointer";
|
||||
|
||||
|
||||
export interface PointerOptions {
|
||||
style: Style;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export class PointerScheduler {
|
||||
private pointers: Pointer[] = [];
|
||||
private prevPointers: Pointer[] = [];
|
||||
private pointerMap: {
|
||||
[key: string]: {
|
||||
pointerConstructor: { new(): Pointer },
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
}
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建指针模型
|
||||
* @param elementList
|
||||
*/
|
||||
constructPointers(elementList: Element[]) {
|
||||
|
||||
}
|
||||
|
||||
setPointerMap(
|
||||
pointerLabel: string,
|
||||
pointerConstructor: { new(): Pointer },
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
) {
|
||||
this.pointerMap[pointerLabel] = {
|
||||
pointerConstructor,
|
||||
zrShapeConstructors,
|
||||
shapeOptions
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
reset() {
|
||||
this.pointers.length = 0;
|
||||
}
|
||||
};
|
18
src/StructV.ts
Normal file
18
src/StructV.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Engine } from "./engine";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const SV = {
|
||||
|
||||
|
||||
createEngine(engineName: string): Engine {
|
||||
return new Engine(engineName);
|
||||
},
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
0
src/View/renderer.ts
Normal file
0
src/View/renderer.ts
Normal file
81
src/View/shape.ts
Normal file
81
src/View/shape.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { Element, Style } from "../Model/element";
|
||||
import { zrShape, ZrShapeConstructor } from "./shapeScheduler";
|
||||
|
||||
|
||||
export interface ShapeStatus {
|
||||
x: number;
|
||||
y: number;
|
||||
rotation: number;
|
||||
zIndex: number;
|
||||
width: number;
|
||||
height: number;
|
||||
content: string;
|
||||
style: Style;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class Shape {
|
||||
id: string = '';
|
||||
type: string = '';
|
||||
zrConstructor: ZrShapeConstructor = null;
|
||||
zrShape: zrShape = null;
|
||||
targetElement: Element = null;
|
||||
|
||||
shapeStatus: ShapeStatus = {
|
||||
x: 0, y: 0,
|
||||
rotation: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
zIndex: 1,
|
||||
content: '',
|
||||
style: {
|
||||
fill: '#000',
|
||||
text: '',
|
||||
textFill: '#000',
|
||||
fontSize: 15,
|
||||
fontWeight: null,
|
||||
stroke: null,
|
||||
opacity: 1,
|
||||
transformText: true,
|
||||
lineWidth: 1
|
||||
}
|
||||
};
|
||||
|
||||
constructor(id: string, zrConstructor: ZrShapeConstructor, element: Element) {
|
||||
this.id = id;
|
||||
this.type = Util.getClassName(zrConstructor);
|
||||
this.targetElement = element;
|
||||
this.zrConstructor = zrConstructor;
|
||||
this.zrShape = new zrConstructor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性
|
||||
* @param propName
|
||||
* @param props
|
||||
* @param sync
|
||||
*/
|
||||
attr(propName: string, props: any, sync: boolean = false) {
|
||||
if(this.shapeStatus[propName] === undefined) return;
|
||||
|
||||
if(propName === 'style') {
|
||||
Util.merge(this.shapeStatus.style, props);
|
||||
}
|
||||
else {
|
||||
this.shapeStatus[propName] = props;
|
||||
}
|
||||
|
||||
if(sync) {
|
||||
this.zrShape.attr(propName, props);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
updateShape() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
96
src/View/shapeScheduler.ts
Normal file
96
src/View/shapeScheduler.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { Util } from "../Common/util";
|
||||
import { Engine } from "../engine";
|
||||
import { Shape } from "./shape";
|
||||
import * as zrender from "zrender";
|
||||
import { Element } from "../Model/element";
|
||||
|
||||
|
||||
export type zrShape = any;
|
||||
export type ZrShapeConstructor = { new(): zrShape };
|
||||
|
||||
|
||||
export class ShapeScheduler {
|
||||
private engine: Engine;
|
||||
private shapeList: Shape[] = [];
|
||||
private shapeTable: { [key: string]: Shape[] } = {};
|
||||
|
||||
private appendList: Shape[] = [];
|
||||
private removeList: Shape[] = [];
|
||||
|
||||
constructor(engine: Engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建图形元素
|
||||
* @param id
|
||||
* @param zrShapeConstructors
|
||||
* @param element
|
||||
*/
|
||||
createShape(id: string, zrShapeConstructors: ZrShapeConstructor, element: Element): Shape {
|
||||
let shapeType = Util.getClassName(zrShapeConstructors),
|
||||
shape = this.getReuseShape(id, shapeType);
|
||||
|
||||
if(shape === null) {
|
||||
shape = new Shape(id, zrShapeConstructors, element);
|
||||
}
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个图形
|
||||
* @param shape
|
||||
*/
|
||||
private appendShape(shape: Shape) {
|
||||
let shapeType = shape.type;
|
||||
|
||||
if(this.shapeTable[shapeType] === undefined) {
|
||||
this.shapeTable[shapeType] = [];
|
||||
}
|
||||
|
||||
this.shapeTable[shapeType].push(shape);
|
||||
this.shapeList.push(shape);
|
||||
this.appendList.push(shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个图形
|
||||
* @param shape
|
||||
*/
|
||||
private removeShape(shape: Shape) {
|
||||
let shapeType = shape.type;
|
||||
|
||||
Util.removeFromList(this.shapeTable[shapeType], item => item.id === shape.id);
|
||||
|
||||
if(this.shapeTable[shapeType].length === 0) {
|
||||
delete this.shapeTable[shapeType];
|
||||
}
|
||||
|
||||
Util.removeFromList(this.shapeList, item => item.id === shape.id);
|
||||
this.removeList.push(shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找可复用的图形
|
||||
* @param id
|
||||
* @param shapeType
|
||||
*/
|
||||
private getReuseShape(id: string, shapeType: string): Shape {
|
||||
if(this.shapeTable[shapeType] !== undefined) {
|
||||
let reuseShape = this.shapeTable[shapeType].find(item => item.id === id);
|
||||
|
||||
if(reuseShape) return reuseShape;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
public reset() {
|
||||
this.appendList.length = 0;
|
||||
this.removeList.length = 0;
|
||||
}
|
||||
};
|
117
src/engine.ts
Normal file
117
src/engine.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { Element } from "./Model/element";
|
||||
import { Sources } from "./sources";
|
||||
import { Pointer } from "./Model/pointer";
|
||||
import { ShapeStatus } from "./View/shape";
|
||||
import { ShapeScheduler, ZrShapeConstructor } from "./View/shapeScheduler";
|
||||
import { ElementConstructor, ElementContainer, ElementScheduler } from "./Model/elementScheduler";
|
||||
import { Link } from "./Model/link";
|
||||
import { LinkScheduler } from "./Model/linkScheduler";
|
||||
import { PointerScheduler } from "./Model/pointerScheduler";
|
||||
|
||||
|
||||
export type LayoutFunction = (elements: ElementContainer | Element[], containerWidth: number, containerHeight: number) => void;
|
||||
|
||||
|
||||
export class Engine {
|
||||
// 引擎id
|
||||
private id: string;
|
||||
// 引擎名称
|
||||
private engineName: string;
|
||||
// HTML容器
|
||||
private DOMContainer: HTMLElement;
|
||||
// 当前保存的源数据
|
||||
private sources: Sources = null;
|
||||
// 序列化的源数据
|
||||
private stringifySources: string = null;
|
||||
|
||||
private elementScheduler: ElementScheduler = null;
|
||||
private linkScheduler: LinkScheduler = null;
|
||||
private pointerScheduler: PointerScheduler = null;
|
||||
private shapeScheduler: ShapeScheduler = null;
|
||||
|
||||
private containerWidth: number;
|
||||
private containerHeight: number;
|
||||
|
||||
private layoutFunction: LayoutFunction;
|
||||
|
||||
constructor(DOMContainer: HTMLElement, engineName: string) {
|
||||
this.engineName = engineName;
|
||||
this.DOMContainer = DOMContainer;
|
||||
this.elementScheduler = new ElementScheduler(this);
|
||||
this.linkScheduler = new LinkScheduler();
|
||||
this.pointerScheduler = new PointerScheduler();
|
||||
this.shapeScheduler = new ShapeScheduler(this);
|
||||
|
||||
this.containerWidth = this.DOMContainer.offsetWidth;
|
||||
this.containerHeight = this.DOMContainer.offsetHeight;
|
||||
}
|
||||
|
||||
public render(sourceData: Sources) {
|
||||
|
||||
if(sourceData === undefined || sourceData === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 若前后数据没有发生变化,什么也不干(将json字符串化后比较)
|
||||
let stringifySources = JSON.stringify(sourceData);
|
||||
if(stringifySources === this.stringifySources) return;
|
||||
this.sources = sourceData;
|
||||
this.stringifySources = stringifySources;
|
||||
|
||||
this.elementScheduler.constructElements(sourceData);
|
||||
this.linkScheduler.constructLinks([]);
|
||||
this.pointerScheduler.constructPointers([]);
|
||||
this.layoutFunction(this.elementScheduler.getElementContainer(), this.containerWidth, this.containerHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elementLabel
|
||||
* @param element
|
||||
* @param zrShapeConstructors
|
||||
* @param shapeOptions
|
||||
*/
|
||||
public applyElement(
|
||||
elementLabel: string,
|
||||
elementConstructor: ElementConstructor,
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
) {
|
||||
this.elementScheduler.setElementMap(elementLabel, elementConstructor, zrShapeConstructors, shapeOptions);
|
||||
}
|
||||
|
||||
|
||||
public applyLink(
|
||||
linkLabel: string, linkConstructor: { new(): Link },
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
) {
|
||||
this.linkScheduler.setLinkMap(linkLabel, linkConstructor, zrShapeConstructors, shapeOptions);
|
||||
}
|
||||
|
||||
public applyPointer(
|
||||
pointerLabel: string, pointerConstructor: { new(): Pointer },
|
||||
zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor,
|
||||
shapeOptions: Partial<ShapeStatus>
|
||||
) {
|
||||
this.pointerScheduler.setPointerMap(pointerLabel, pointerConstructor, zrShapeConstructors, shapeOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置布局函数
|
||||
* @param layoutFunction
|
||||
*/
|
||||
public applyLayout(layoutFunction: LayoutFunction) {
|
||||
this.layoutFunction = layoutFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置引擎数据
|
||||
*/
|
||||
private resetData() {
|
||||
this.elementScheduler.reset();
|
||||
this.linkScheduler.reset();
|
||||
this.pointerScheduler.reset();
|
||||
this.shapeScheduler.reset();
|
||||
}
|
||||
};
|
26
src/sources.ts
Normal file
26
src/sources.ts
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
// 连接目标信息
|
||||
export type LinkTarget = {
|
||||
element: string;
|
||||
target: number | string;
|
||||
[key: string]: any;
|
||||
} | number | string;
|
||||
|
||||
// 结点连接声明
|
||||
export type LinkData = LinkTarget | LinkTarget[];
|
||||
|
||||
// 结点指针声明
|
||||
export type PointerData = string | string[];
|
||||
|
||||
// 源数据单元
|
||||
export interface SourceElement {
|
||||
id: string | number;
|
||||
[key: string]: any | LinkData | PointerData;
|
||||
}
|
||||
|
||||
// 源数据格式
|
||||
export type Sources = { } | SourceElement[];
|
||||
|
||||
|
||||
|
||||
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"module": "commonJS",
|
||||
"experimentalDecorators": true,
|
||||
//"outDir": "./../Demos/src/StructV",
|
||||
"outDir": "./../Visualizer/src/StructV",
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules", "examples"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
28
webpack.config.js
Normal file
28
webpack.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
const path = require('path');
|
||||
|
||||
|
||||
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: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'awesome-typescript-loader',
|
||||
options: {
|
||||
configFileName: './atlconfig.json'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
//devtool: 'eval-source-map'
|
||||
};
|
Loading…
Reference in New Issue
Block a user