From b886f33d9ea7fa96a7de3252448d8c7f05fd0623 Mon Sep 17 00:00:00 2001 From: Phenom <1543046129@qq.com> Date: Wed, 3 Feb 2021 18:44:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=9E=B6=E6=9E=84=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/util.ts | 8 ++ src/Model/element.ts | 6 +- src/Model/elementScheduler.ts | 26 ++-- src/Model/link.ts | 41 ++++--- src/Model/linkScheduler.ts | 34 +++-- src/Model/pointer.ts | 17 ++- src/View/reconciler.ts | 225 ++++++++++++++++++++++++++++++++++ src/View/renderer.ts | 14 +++ src/View/shape.ts | 12 +- src/View/shapeScheduler.ts | 34 ++++- src/engine.ts | 41 ++++++- 11 files changed, 406 insertions(+), 52 deletions(-) create mode 100644 src/View/reconciler.ts diff --git a/src/Common/util.ts b/src/Common/util.ts index 8c0978c..1b44543 100644 --- a/src/Common/util.ts +++ b/src/Common/util.ts @@ -36,6 +36,14 @@ export const Util = { zrender.util.merge(origin, dest, true); }, + /** + * 拷贝对象 + * @param object + */ + clone(object) { + return zrender.util.clone(object); + }, + /** * 从列表中移除元素 * @param list 移除列表 diff --git a/src/Model/element.ts b/src/Model/element.ts index 701fc09..c56b6a6 100644 --- a/src/Model/element.ts +++ b/src/Model/element.ts @@ -44,16 +44,16 @@ export interface ElementStatus { export class Element { - id: any; + id: number; elementId: string = null; elementLabel: string = null; elementStatus: ElementStatus = null; - zrShape: zrShape[] = []; + shapes: Shape[] | Shape = null; relativeLinks: Link[] = []; relativePointers: Pointer[] = []; - isDirty: boolean= false; + isDirty: boolean = false; // 给sourceElement的部分 [key: string]: any; diff --git a/src/Model/elementScheduler.ts b/src/Model/elementScheduler.ts index 3126252..dc468aa 100644 --- a/src/Model/elementScheduler.ts +++ b/src/Model/elementScheduler.ts @@ -1,7 +1,7 @@ import { Engine } from "../engine"; import { SourceElement, Sources } from "../sources"; import { Shape, ShapeStatus } from "../View/shape"; -import { ZrShapeConstructor } from "../View/shapeScheduler"; +import { ShapeScheduler, ZrShapeConstructor } from "../View/shapeScheduler"; import { Element } from "./element"; @@ -12,6 +12,8 @@ export type ElementConstructor = { new(elementLabel: string, sourceElement: Sour export class ElementScheduler { private engine: Engine; + + private shapeScheduler: ShapeScheduler; // 元素队列 private elementList: Element[] = []; // 元素容器,即源数据经element包装后的结构 @@ -25,8 +27,9 @@ export class ElementScheduler { } }; - constructor(engine: Engine) { + constructor(engine: Engine, shapeScheduler: ShapeScheduler) { this.engine = engine; + this.shapeScheduler = shapeScheduler; } /** @@ -103,24 +106,31 @@ export class ElementScheduler { element.applyShapeOptions(shapeOptions); if(Array.isArray(zrShapeConstructors)) { - shapes = zrShapeConstructors.map((item, index) => new Shape(`${elementId}(${index})`, item, element)); + shapes = zrShapeConstructors.map(function(item, index) { + return this.shapeScheduler.createShape(`${elementId}(${index})`, item, element) + }); + + this.shapeScheduler.packShapes(shapes); } else { - shapes = new Shape(`elementId`, zrShapeConstructors, element); + shapes = this.shapeScheduler.createShape(`elementId`, zrShapeConstructors, element); } - element.defineShape(shapes, element.elementStatus); + element.shapes = shapes; + element.renderShape(shapes, element.elementStatus); return element; } - + /** + * 更新element对应的图形 + */ public updateShapes() { for(let i = 0; i < this.elementList.length; i++) { let ele = this.elementList[i]; if(ele.isDirty) { - ele.renderShape(); + ele.renderShape(ele.zrShapes, ele.elementStatus); } } } @@ -150,6 +160,6 @@ export class ElementScheduler { */ public reset() { this.elementList.length = 0; - this.elementContainer = {}; + this.elementContainer = { }; } }; \ No newline at end of file diff --git a/src/Model/link.ts b/src/Model/link.ts index c71d34f..31cf22f 100644 --- a/src/Model/link.ts +++ b/src/Model/link.ts @@ -1,37 +1,36 @@ import { LinkTarget } from "../sources"; +import { Shape } from "../View/shape"; import { zrShape } from "../View/shapeScheduler"; -import { Element, Style } from "./element"; +import { Element, ElementStatus, Style } from "./element"; import { LabelStyle } from "./pointer"; + export interface LinkOptions { style: Style; labelStyle: LabelStyle; }; +export interface LinkStatus extends ElementStatus { + points: [number, number][]; +}; + export class Link { - // 连线 id id: string; - // 连线起始 element - element: Element; - // 连线目标 element - target: Element; - // 连线类型名称 - linkName: string; - // 连线图形实例 - zrShapes: zrShape[]; - // 连线序号 - index: number; - // 连线在源数据的声明 - sourceLinkTarget: LinkTarget; + element: Element = null; + target: Element = null; + linkLabel: string = null; + linkStatus: LinkStatus; + + shapes: Shape[] | Shape = []; + index: number = -1; + sourceLinkTarget: LinkTarget = null; isDirty: boolean = false; - constructor() { - - } + constructor() { } /** * 元素是否为脏 @@ -40,4 +39,12 @@ export class Link { setDirty(isDirty: boolean) { this.isDirty = isDirty; } + + /** + * 定义该element映射的图形 + * @param shapes + * @param elementStatus + * @override + */ + renderShape(shapes: Shape[] | Shape, elementStatus: ElementStatus) { } }; \ No newline at end of file diff --git a/src/Model/linkScheduler.ts b/src/Model/linkScheduler.ts index 2efedc4..1ef5a9a 100644 --- a/src/Model/linkScheduler.ts +++ b/src/Model/linkScheduler.ts @@ -1,19 +1,15 @@ import { ShapeStatus } from "../View/shape"; import { ZrShapeConstructor } from "../View/shapeScheduler"; -import { Element, Style } from "./element"; +import { Element } 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 }, @@ -30,11 +26,18 @@ export class LinkScheduler { * 构建链接模型 * @param elementList */ - constructLinks(elementList: Element[]) { + public constructLinks(elementList: Element[]) { } - setLinkMap( + /** + * + * @param linkLabel + * @param linkConstructor + * @param zrShapeConstructors + * @param shapeOptions + */ + public setLinkMap( linkLabel: string, linkConstructor: { new(): Link }, zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor, @@ -47,7 +50,20 @@ export class LinkScheduler { }; } - reset() { + /** + * 更新element对应的图形 + */ + public updateShapes() { + for(let i = 0; i < this.links.length; i++) { + let link = this.links[i]; + + if(link.isDirty) { + link.renderShape(link.shapes, link.linkStatus); + } + } + } + + public reset() { this.links.length = 0; } } \ No newline at end of file diff --git a/src/Model/pointer.ts b/src/Model/pointer.ts index 21460d0..63a6b21 100644 --- a/src/Model/pointer.ts +++ b/src/Model/pointer.ts @@ -1,5 +1,6 @@ +import { Shape } from "../View/shape"; import { zrShape } from "../View/shapeScheduler"; -import { Style } from "./element"; +import { ElementStatus, Style } from "./element"; export interface LabelStyle extends Style { @@ -19,7 +20,7 @@ export class Pointer { // 指针 id id: string; // 指针图形实例 - zrShapes: zrShape[]; + shape: Shape; // 指针类型名称 pointerLabel: string; // 被该指针合并的其他指针 @@ -30,9 +31,9 @@ export class Pointer { // 指针标签内容 text: string; // 指针标签图形实例 - textZrShapes: Text[]; + textZrShapes: Shape[]; // 逗号图形实例 - commaShapes: Text[]; + commaShapes: Shape[]; // 目标 element target: Element; @@ -50,4 +51,12 @@ export class Pointer { setDirty(isDirty: boolean) { this.isDirty = isDirty; } + + /** + * 定义该element映射的图形 + * @param shapes + * @param elementStatus + * @override + */ + renderShape(shapes: Shape[] | Shape, elementStatus: ElementStatus) { } }; \ No newline at end of file diff --git a/src/View/reconciler.ts b/src/View/reconciler.ts new file mode 100644 index 0000000..ba66924 --- /dev/null +++ b/src/View/reconciler.ts @@ -0,0 +1,225 @@ +import { Util } from "../Common/util"; +import { Style } from "../Model/element"; +import { Shape } from "./shape"; +import { ShapeScheduler } from "./shapeScheduler"; + + + +export enum patchType { + ADD, + REMOVE, + POSITION, + PATH, + ROTATION, + SIZE, + STYLE +} + + +export interface patchInfo { + type: number; + shape: Shape; +} + + +export class Reconciler { + private shapeScheduler: ShapeScheduler; + + constructor(shapeScheduler: ShapeScheduler) { + this.shapeScheduler = shapeScheduler; + } + + /** + * 进行图形样式对象的比较 + * @param oldStyle + * @param newStyle + */ + reconcileStyle(oldStyle: Style, newStyle: Style): {name: string, old: any, new: any }[] { + let styleName: {name: string, old: any, new: any }[] = []; + + Object.keys(newStyle).map(prop => { + if(newStyle[prop] !== oldStyle[prop]) { + styleName.push({ + name: prop, + old: oldStyle[prop], + new: newStyle[prop] + }); + } + }); + + return styleName; + } + + /** + * 图形间的 differ + * @param shape + */ + reconcileShape(shape: Shape) { + let patchList: patchInfo[] = []; + + if(shape.isDirty === false) return; + + // 比较图形路径 + if(JSON.stringify(shape.prevShapeStatus) !== JSON.stringify(shape.shapeStatus.points)) { + patchList.push({ + type: patchType.PATH, + shape + }); + } + + // 比较图形坐标位置 + if(shape.prevShapeStatus.x !== shape.shapeStatus.x || shape.prevShapeStatus.y !== shape.shapeStatus.y) { + patchList.push({ + type: patchType.POSITION, + shape + }); + } + + // 比较旋转角度 + if(shape.prevShapeStatus.rotation !== shape.shapeStatus.rotation) { + patchList.push({ + type: patchType.ROTATION, + shape + }); + } + + // 比较尺寸 + if(shape.prevShapeStatus.width !== shape.shapeStatus.width || shape.prevShapeStatus.height !== shape.shapeStatus.height) { + patchList.push({ + type: patchType.SIZE, + shape + }); + } + + // 比较样式 + let style = this.reconcileStyle(shape.prevShapeStatus.style, shape.shapeStatus.style); + if(style.length) { + patchList.push({ + type: patchType.STYLE, + shape + }); + } + + // 对变化进行更新 + this.patch(patchList); + } + + /** + * + * @param container + * @param shapeList + */ + reconcileShapeList(container: { [key: string]: Shape[] }, shapeList: Shape[]) { + let patchList: patchInfo[] = []; + + for(let i = 0; i < shapeList.length; i++) { + let shape = shapeList[i], + name = shape.type; + + // 若发现存在于新视图模型而不存在于旧视图模型的图形,则该图形都标记为 ADD + if(container[name] === undefined) { + patchList.push({ + type: patchType.ADD, + shape + }); + } + else { + let oldShape = container[name].find(item => item.id === shape.id); + + // 若旧图形列表存在对应的图形,进行 shape 间 differ + if(oldShape) { + oldShape.isReconcilerVisited = true; + this.reconcileShape(shape); + } + // 若发现存在于新视图模型而不存在于旧视图模型的图形,则该图形都标记为 ADD + else { + patchList.push({ + type: patchType.ADD, + shape + }); + } + } + } + + // 在旧视图容器中寻找未访问过的图形,表明该图形该图形需要移除 + Object.keys(container).forEach(key => { + container[key].forEach(shape => { + if(shape.isReconcilerVisited === false) { + patchList.push({ + type: patchType.REMOVE, + shape, + }); + } + + shape.isReconcilerVisited = false; + }); + }); + + this.patch(patchList); + } + + + /** + * 对修改的视图进行补丁更新 + * @param patchList + */ + patch(patchList: patchInfo[]) { + let patch: patchInfo, + shape: Shape, + i; + + for(i = 0; i < patchList.length; i++) { + patch = patchList[i]; + shape = patch.shape; + + switch(patch.type) { + case patchType.ADD: { + this.shapeScheduler.appendShape(shape); + this.shapeScheduler.emitAnimation(shape, 'append'); + break; + } + + case patchType.REMOVE: { + this.shapeScheduler.removeShape(shape); + this.shapeScheduler.emitAnimation(shape, 'remove'); + break; + } + + case patchType.PATH: { + shape.prevShapeStatus.points = shape.shapeStatus.points; + this.shapeScheduler.emitAnimation(shape, 'path'); + } + + case patchType.POSITION: { + shape.prevShapeStatus.x = shape.shapeStatus.x; + shape.prevShapeStatus.y = shape.shapeStatus.y; + this.shapeScheduler.emitAnimation(shape, 'position'); + break; + } + + case patchType.ROTATION: { + shape.prevShapeStatus.rotation = shape.shapeStatus.rotation; + this.shapeScheduler.emitAnimation(shape, 'rotation'); + break; + } + + case patchType.SIZE: { + shape.prevShapeStatus.width = shape.shapeStatus.width; + shape.prevShapeStatus.height = shape.shapeStatus.height; + this.shapeScheduler.emitAnimation(shape, 'size'); + break; + } + + case patchType.STYLE: { + shape.prevShapeStatus.style = Util.clone(shape.shapeStatus.style); + this.shapeScheduler.emitAnimation(shape, 'style'); + break; + } + + default: { + break; + } + } + } + } +} \ No newline at end of file diff --git a/src/View/renderer.ts b/src/View/renderer.ts index e69de29..1601e98 100644 --- a/src/View/renderer.ts +++ b/src/View/renderer.ts @@ -0,0 +1,14 @@ + + + + + +export class Renderer { + constructor() { + + } + + applyAnimation() { + + } +} \ No newline at end of file diff --git a/src/View/shape.ts b/src/View/shape.ts index b394786..649a751 100644 --- a/src/View/shape.ts +++ b/src/View/shape.ts @@ -1,6 +1,6 @@ import { Util } from "../Common/util"; import { Element, Style } from "../Model/element"; -import { zrShape, ZrShapeConstructor } from "./shapeScheduler"; +import { Group, zrShape, ZrShapeConstructor } from "./shapeScheduler"; export interface ShapeStatus { @@ -11,6 +11,7 @@ export interface ShapeStatus { width: number; height: number; content: string; + points: [number, number][]; style: Style; }; @@ -22,7 +23,8 @@ export class Shape { zrConstructor: ZrShapeConstructor = null; zrShape: zrShape = null; targetElement: Element = null; - + parentGroup: Group = null; + shapeStatus: ShapeStatus = { x: 0, y: 0, rotation: 0, @@ -30,6 +32,7 @@ export class Shape { height: 0, zIndex: 1, content: '', + points: [], style: { fill: '#000', text: '', @@ -43,12 +46,17 @@ export class Shape { } }; + prevShapeStatus: ShapeStatus = null; + isDirty: boolean = false; + isReconcilerVisited: boolean = false; + 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(); + this.prevShapeStatus = Util.clone(this.shapeStatus); } /** diff --git a/src/View/shapeScheduler.ts b/src/View/shapeScheduler.ts index 6ea5ae6..38b94ed 100644 --- a/src/View/shapeScheduler.ts +++ b/src/View/shapeScheduler.ts @@ -6,6 +6,7 @@ import { Element } from "../Model/element"; export type zrShape = any; +export type Group = any; export type ZrShapeConstructor = { new(): zrShape }; @@ -14,6 +15,7 @@ export class ShapeScheduler { private shapeList: Shape[] = []; private shapeTable: { [key: string]: Shape[] } = {}; + private parentGroupList: Group[] = []; private appendList: Shape[] = []; private removeList: Shape[] = []; @@ -27,7 +29,7 @@ export class ShapeScheduler { * @param zrShapeConstructors * @param element */ - createShape(id: string, zrShapeConstructors: ZrShapeConstructor, element: Element): Shape { + public createShape(id: string, zrShapeConstructors: ZrShapeConstructor, element: Element): Shape { let shapeType = Util.getClassName(zrShapeConstructors), shape = this.getReuseShape(id, shapeType); @@ -38,11 +40,28 @@ export class ShapeScheduler { return shape; } + /** + * + * @param shapes + */ + public packShapes(shapes: Shape[]){ + let group: Group = new zrender.Group(), + shape: Shape; + + for(let i = 0; i < shapes.length; i++) { + shape = shapes[i]; + group.add(shape.zrShape); + shape.parentGroup = group; + } + + this.parentGroupList.push(group); + } + /** * 添加一个图形 * @param shape */ - private appendShape(shape: Shape) { + public appendShape(shape: Shape) { let shapeType = shape.type; if(this.shapeTable[shapeType] === undefined) { @@ -58,7 +77,7 @@ export class ShapeScheduler { * 移除一个图形 * @param shape */ - private removeShape(shape: Shape) { + public removeShape(shape: Shape) { let shapeType = shape.type; Util.removeFromList(this.shapeTable[shapeType], item => item.id === shape.id); @@ -71,6 +90,15 @@ export class ShapeScheduler { this.removeList.push(shape); } + /** + * 发起一个动画请求 + * @param shape + * @param animationType + */ + public emitAnimation(shape: Shape, animationType: string) { + + } + /** * 查找可复用的图形 * @param id diff --git a/src/engine.ts b/src/engine.ts index 7612ce4..22f9dd3 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -37,15 +37,19 @@ export class Engine { constructor(DOMContainer: HTMLElement, engineName: string) { this.engineName = engineName; this.DOMContainer = DOMContainer; - this.elementScheduler = new ElementScheduler(this); + this.shapeScheduler = new ShapeScheduler(this); + this.elementScheduler = new ElementScheduler(this, this.shapeScheduler); this.linkScheduler = new LinkScheduler(); this.pointerScheduler = new PointerScheduler(); - this.shapeScheduler = new ShapeScheduler(this); this.containerWidth = this.DOMContainer.offsetWidth; this.containerHeight = this.DOMContainer.offsetHeight; } + /** + * + * @param sourceData + */ public render(sourceData: Sources) { if(sourceData === undefined || sourceData === null) { @@ -58,9 +62,7 @@ export class Engine { this.sources = sourceData; this.stringifySources = stringifySources; - this.elementScheduler.constructElements(sourceData); - this.linkScheduler.constructLinks([]); - this.pointerScheduler.constructPointers([]); + this.constructModel(sourceData); this.layoutFunction(this.elementScheduler.getElementContainer(), this.containerWidth, this.containerHeight); } @@ -80,7 +82,13 @@ export class Engine { this.elementScheduler.setElementMap(elementLabel, elementConstructor, zrShapeConstructors, shapeOptions); } - + /** + * 应用一个Link模型 + * @param linkLabel + * @param linkConstructor + * @param zrShapeConstructors + * @param shapeOptions + */ public applyLink( linkLabel: string, linkConstructor: { new(): Link }, zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor, @@ -89,6 +97,13 @@ export class Engine { this.linkScheduler.setLinkMap(linkLabel, linkConstructor, zrShapeConstructors, shapeOptions); } + /** + * 应用一个Pointer模型 + * @param pointerLabel + * @param pointerConstructor + * @param zrShapeConstructors + * @param shapeOptions + */ public applyPointer( pointerLabel: string, pointerConstructor: { new(): Pointer }, zrShapeConstructors: ZrShapeConstructor[] | ZrShapeConstructor, @@ -105,6 +120,20 @@ export class Engine { this.layoutFunction = layoutFunction; } + /** + * 构建模型 + * @param sourceData + */ + private constructModel(sourceData: Sources) { + this.elementScheduler.constructElements(sourceData); + this.linkScheduler.constructLinks([]); + this.pointerScheduler.constructPointers([]); + } + + private updateShapes() { + + } + /** * 重置引擎数据 */