import { IGroup } from '@antv/g-base' ; import { each , isPlainObject , isString , isBoolean , mix , deepMix , clone } from '@antv/util' ; import { IItemBase , IItemBaseConfig } from '../interface/item' ; import Shape from '../element/shape' ; import { IBBox , IPoint , IShapeBase , ModelConfig , ShapeStyle , Indexable , NodeConfig , EdgeConfig , ComboConfig , ITEM_TYPE , UpdateType , } from '../types' ; import { getBBox } from '../util/graphic' ; import { translate } from '../util/math' ; import { uniqueId } from '../util/base' ; const CACHE_BBOX = 'bboxCache' ; const CACHE_CANVAS_BBOX = 'bboxCanvasCache' ; const ARROWS = [ 'startArrow' , 'endArrow' ] ; export default class ItemBase implements IItemBase { public _cfg : IItemBaseConfig & { [ key ] : unknown ; } = { } ; public destroyed : boolean = false ; constructor ( cfg ) { const defaultCfg = { type { string } id : undefined , type { string } type : 'item' , type { object } model : { } as ModelConfig , type { G . Group } group : undefined , type { boolean } animate : false , type { boolean } visible : true , type { boolean } locked : false , type { boolean } event : true , type object keyShape : undefined , type Array states : [ ] , } ; this . _cfg = Object . assign ( defaultCfg , this . getDefaultCfg ( ) , cfg ) ; const model = this . get ( 'model' ) ; let { id } = model ; const itemType = this . get ( 'type' ) ; if ( ! id ) { id = uniqueId ( itemType ) ; this . get ( 'model' ) . id = id ; } this . set ( 'id' , id ) ; const { group } = cfg ; if ( group ) { group . set ( 'item' , this ) ; group . set ( 'id' , id ) ; } this . init ( ) ; this . draw ( ) ; const shapeType = ( model . shape as string ) || ( model . type as string ) || ( itemType === 'edge' ? 'line' : 'circle' ) ; const shapeFactory = this . get ( 'shapeFactory' ) ; if ( shapeFactory && shapeFactory [ shapeType ] ) { const { options } = shapeFactory [ shapeType ] ; if ( options && options . stateStyles ) { let styles = this . get ( 'styles' ) || model . stateStyles ; styles = deepMix ( { } , options . stateStyles , styles ) ; this . set ( 'styles' , styles ) ; } } } /**
     * 根据 keyshape 计算包围盒
     */ private calculateBBox ( ) { const keyShape = this . get ( 'keyShape' ) ; const group = this . get ( 'group' ) ; const bbox = getBBox ( keyShape , group ) ; bbox . x = bbox . minX ; bbox . y = bbox . minY ; bbox . width = bbox . maxX - bbox . minX ; bbox . height = bbox . maxY - bbox . minY ; bbox . centerX = ( bbox . minX + bbox . maxX ) / 2 ; bbox . centerY = ( bbox . minY + bbox . maxY ) / 2 ; return bbox ; } /**
     * 根据 keyshape 计算包围盒
     */ public calculateCanvasBBox ( ) { const keyShape = this . get ( 'keyShape' ) ; const group = this . get ( 'group' ) ; const bbox = getBBox ( keyShape , group ) ; bbox . x = bbox . minX ; bbox . y = bbox . minY ; bbox . width = bbox . maxX - bbox . minX ; bbox . height = bbox . maxY - bbox . minY ; bbox . centerX = ( bbox . minX + bbox . maxX ) / 2 ; bbox . centerY = ( bbox . minY + bbox . maxY ) / 2 ; return bbox ; } /**
     * draw shape
     */ private drawInner ( ) { const self = this ; const shapeFactory = self . get ( 'shapeFactory' ) ; const group = self . get ( 'group' ) ; const model = self . get ( 'model' ) ; group . clear ( ) ; const visible = model . visible ; if ( visible !== undefined && ! visible ) self . changeVisibility ( visible ) ; if ( ! shapeFactory ) { return ; } self . updatePosition ( model ) ; const cfg = self . getShapeCfg ( model ) ; const shapeType = cfg . type as string ; const keyShape = shapeFactory . draw ( shapeType , cfg , group ) ; if ( keyShape ) { self . set ( 'keyShape' , keyShape ) ; keyShape . set ( 'isKeyShape' , true ) ; keyShape . set ( 'draggable' , true ) ; } this . setOriginStyle ( ) ; this . set ( 'currentShape' , shapeType ) ; this . restoreStates ( shapeFactory , shapeType ! ) ; } param keyShape param group public setOriginStyle ( ) { const group = this . get ( 'group' ) ; const children = group . get ( 'children' ) ; const keyShape = this . getKeyShape ( ) ; const self = this ; const keyShapeName = keyShape . get ( 'name' ) ; if ( ! this . get ( 'originStyle' ) ) { const originStyles = { } ; for ( let i = 0 ; i < children . length ; i ++ ) { const child = children [ i ] ; const shapeType = child . get ( 'type' ) ; const name = child . get ( 'name' ) ; if ( name && name !== keyShapeName ) { originStyles [ name ] = shapeType !== 'image' ? clone ( child . attr ( ) ) : self . getShapeStyleByName ( name ) ; if ( shapeType === 'text' && originStyles [ name ] ) { delete originStyles [ name ] . x ; delete originStyles [ name ] . y ; delete originStyles [ name ] . matrix ; } } else { const keyShapeStyle = self . getShapeStyleByName ( ) ; delete keyShapeStyle . path ; delete keyShapeStyle . matrix ; if ( ! keyShapeName ) { Object . assign ( originStyles , keyShapeStyle ) ; } else { if ( ! name ) { const shapeName = uniqueId ( 'shape' ) ; child . set ( 'name' , shapeName ) ; group [ 'shapeMap' ] [ shapeName ] = child ; originStyles [ shapeName ] = shapeType !== 'image' ? clone ( child . attr ( ) ) : self . getShapeStyleByName ( name ) ; } else { originStyles [ keyShapeName ] = keyShapeStyle ; } } } } self . set ( 'originStyle' , originStyles ) ; } else { const styles = this . get ( 'originStyle' ) ; if ( keyShapeName && ! styles [ keyShapeName ] ) styles [ keyShapeName ] = { } ; const currentStatesStyle = this . getCurrentStatesStyle ( ) ; for ( let i = 0 ; i < children . length ; i ++ ) { const child = children [ i ] ; const name = child . get ( 'name' ) ; const shapeAttrs = child . attr ( ) ; if ( name && name !== keyShapeName ) { const shapeStateStyle = currentStatesStyle [ name ] ; if ( ! styles [ name ] ) styles [ name ] = { } ; if ( shapeStateStyle ) { Object . keys ( shapeAttrs ) . forEach ( ( key ) => { const value = shapeAttrs [ key ] ; if ( value !== shapeStateStyle [ key ] ) styles [ name ] [ key ] = value ; } ) ; } else { styles [ name ] = child . get ( 'type' ) !== 'image' ? clone ( shapeAttrs ) : self . getShapeStyleByName ( name ) ; } } else { const shapeAttrs = child . attr ( ) ; const keyShapeStateStyles = { } ; Object . keys ( currentStatesStyle ) . forEach ( styleKey => { const subStyle = currentStatesStyle [ styleKey ] ; if ( styleKey === keyShapeName || ! isPlainObject ( subStyle ) ) { keyShapeStateStyles [ styleKey ] = subStyle ; } } ) ; Object . keys ( shapeAttrs ) . forEach ( ( key ) => { const value = shapeAttrs [ key ] ; if ( keyShapeStateStyles [ key ] !== value ) { if ( keyShapeName ) styles [ keyShapeName ] [ key ] = value ; else styles [ key ] = value ; } } ) ; } } delete styles . path ; delete styles . matrix ; delete styles . x ; delete styles . y ; if ( styles [ keyShapeName ] ) { delete styles [ keyShapeName ] . x ; delete styles [ keyShapeName ] . y ; delete styles [ keyShapeName ] . matrix ; delete styles [ keyShapeName ] . path ; } self . set ( 'originStyle' , styles ) ; } } param shapeFactory param shapeType private restoreStates ( shapeFactory , shapeType ) { const self = this ; const states = self . get ( 'states' ) ; each ( states , ( state ) => { shapeFactory . setState ( shapeType , state , true , self ) ; } ) ; } protected init ( ) { const shapeFactory = Shape . getFactory ( this . get ( 'type' ) ) ; this . set ( 'shapeFactory' , shapeFactory ) ; } internal param { String } key return { object | string | number } public get < T = any > ( key ) { return this . _cfg [ key ] as T ; } internal param { String | Object } key param { object | string | number } val public set ( key , val ? ) { if ( isPlainObject ( key ) ) { this . _cfg = { ... this . _cfg , ... key } ; } else { this . _cfg [ key ] = val ; } } protected getDefaultCfg ( ) { return { } ; } /**
     * 更新/刷新等操作后，清除 cache
     */ protected clearCache ( ) { this . set ( CACHE_BBOX , null ) ; this . set ( CACHE_CANVAS_BBOX , null ) ; } /**
     * 渲染前的逻辑，提供给子类复写
     */ protected beforeDraw ( ) { } /**
     * 渲染后的逻辑，提供给子类复写
     */ protected afterDraw ( ) { } /**
     * 更新后做一些工作
     */ protected afterUpdate ( ) { } /**
     * draw shape
     */ public draw ( ) { this . beforeDraw ( ) ; this . drawInner ( ) ; this . afterDraw ( ) ; } public getShapeStyleByName ( name ? ) { const group = this . get ( 'group' ) ; let currentShape ; if ( name ) { currentShape = group [ 'shapeMap' ] [ name ] ; } else { currentShape = this . getKeyShape ( ) ; } if ( currentShape ) { const styles = { } ; each ( currentShape . attr ( ) , ( val , key ) => { if ( key !== 'img' || isString ( val ) ) { styles [ key ] = val ; } } ) ; return styles ; } return { } ; } public getShapeCfg ( model , updateType ? ) { const styles = this . get ( 'styles' ) ; if ( styles ) { const newModel = model ; newModel . style = { ... styles , ... model . style } ; return newModel ; } return model ; } param state public getStateStyle ( state ) { const styles = this . get ( 'styles' ) ; const stateStyle = styles && styles [ state ] ; return stateStyle ; } /**
     * get keyshape style
     */ public getOriginStyle ( ) { return this . get ( 'originStyle' ) ; } public getCurrentStatesStyle ( ) { const self = this ; let styles = { } ; const states = self . getStates ( ) ; if ( ! states || ! states . length ) { return this . get ( 'originStyle' ) ; } each ( self . getStates ( ) , ( state ) => { styles = Object . assign ( styles , self . getStateStyle ( state ) ) ; } ) ; return styles ; } internal param { String } state param { Boolean } value public setState ( state , value ) { const states = this . get ( 'states' ) ; const shapeFactory = this . get ( 'shapeFactory' ) ; let stateName = state ; let filterStateName = state ; if ( isString ( value ) ) { stateName = `${ state }:${ value }` ; filterStateName = `${ state }:` ; } let newStates = states ; if ( isBoolean ( value ) ) { const index = states . indexOf ( filterStateName ) ; if ( value ) { if ( index > - 1 ) { return ; } states . push ( stateName ) ; } else if ( index > - 1 ) { states . splice ( index , 1 ) ; } } else if ( isString ( value ) ) { const filterStates = states . filter ( ( name ) => name . includes ( filterStateName ) ) ; if ( filterStates . length > 0 ) { this . clearStates ( filterStates ) ; } newStates = newStates . filter ( ( name ) => ! name . includes ( filterStateName ) ) ; newStates . push ( stateName ) ; this . set ( 'states' , newStates ) ; } if ( shapeFactory ) { const model = this . get ( 'model' ) ; const type = model . type ; shapeFactory . setState ( type , state , value , this ) ; } } param states public clearStates ( states ? ) { const self = this ; const originStates = self . getStates ( ) ; const shapeFactory = self . get ( 'shapeFactory' ) ; const model = self . get ( 'model' ) ; const shape = model . type ; if ( ! states ) { states = originStates ; } if ( isString ( states ) ) { states = [ states ] ; } const newStates = originStates . filter ( ( state ) => states . indexOf ( state ) === - 1 ) ; self . set ( 'states' , newStates ) ; states . forEach ( ( state ) => { shapeFactory . setState ( shape , state , false , self ) ; } ) ; } return { G . Group } public getContainer ( ) { return this . get ( 'group' ) ; } return { IShapeBase } public getKeyShape ( ) { return this . get ( 'keyShape' ) ; } return { Object } public getModel ( ) { return this . get ( 'model' ) ; } return { string } public getType ( ) { return this . get ( 'type' ) ; } /**
     * 获取 Item 的ID
     */ public getID ( ) { return this . get ( 'id' ) ; } /**
     * 是否是 Item 对象，悬空边情况下进行判定
     */ public isItem ( ) { return true ; } return { Array } public getStates ( ) { return this . get ( 'states' ) ; } param { String } state return { Boolean } public hasState ( state ) { const states = this . getStates ( ) ; return states . indexOf ( state ) >= 0 ; } /**
     * 刷新一般用于处理几种情况
     * 1. item model 在外部被改变
     * 2. 边的节点位置发生改变，需要重新计算边
     *
     * 因为数据从外部被修改无法判断一些属性是否被修改，直接走位置和 shape 的更新
     */ public refresh ( updateType ? ) { const model = this . get ( 'model' ) ; this . updatePosition ( model ) ; this . updateShape ( updateType ) ; this . afterUpdate ( ) ; this . clearCache ( ) ; } public getUpdateType ( cfg ? ) { return undefined ; } internal param { Object } cfg public update ( cfg , updateType = undefined ) { const model = this . get ( 'model' ) ; if ( updateType === 'move' ) { this . updatePosition ( cfg ) ; } else { const oriVisible = model . visible ; const cfgVisible = cfg . visible ; if ( oriVisible !== cfgVisible && cfgVisible !== undefined ) this . changeVisibility ( cfgVisible ) ; const originPosition = { x : model . x ! , y : model . y ! } ; cfg . x = isNaN ( + cfg . x ) ? model . x : ( + cfg . x ) ; cfg . y = isNaN ( + cfg . y ) ? model . y : ( + cfg . y ) ; const styles = this . get ( 'styles' ) ; if ( cfg . stateStyles ) { const { stateStyles } = cfg ; mix ( styles , stateStyles ) ; delete cfg . stateStyles ; } Object . assign ( model , cfg ) ; if ( originPosition . x !== cfg . x || originPosition . y !== cfg . y ) { this . updatePosition ( cfg ) ; } this . updateShape ( updateType ) ; } this . afterUpdate ( ) ; this . clearCache ( ) ; } /**
     * 更新元素内容，样式
     */ public updateShape ( updateType ? ) { const shapeFactory = this . get ( 'shapeFactory' ) ; const model = this . get ( 'model' ) ; const shape = model . type ; if ( shapeFactory . shouldUpdate ( shape ) && shape === this . get ( 'currentShape' ) ) { const updateCfg = this . getShapeCfg ( model , updateType ) ; shapeFactory . baseUpdate ( shape , updateCfg , this , updateType ) ; if ( updateType !== 'move' ) this . setOriginStyle ( ) ; } else { this . draw ( ) ; } this . restoreStates ( shapeFactory , shape ) ; } param { object } cfg public updatePosition ( cfg ) { const model = this . get ( 'model' ) ; const x = isNaN ( + cfg . x ) ? ( + model . x ) : ( + cfg . x ) ; const y = isNaN ( + cfg . y ) ? ( + model . y ) : ( + cfg . y ) ; const group = this . get ( 'group' ) ; if ( isNaN ( x ) || isNaN ( y ) ) { return false ; } model . x = x ; model . y = y ; const matrix = group . getMatrix ( ) ; if ( matrix && matrix [ 6 ] === x && matrix [ 7 ] === y ) return false ; group . resetMatrix ( ) ; translate ( group , { x : x ! , y : y ! } ) ; this . clearCache ( ) ; return true ; } return { Object } public getBBox ( ) { let bbox = this . get ( CACHE_BBOX ) ; if ( ! bbox ) { bbox = this . calculateBBox ( ) ; this . set ( CACHE_BBOX , bbox ) ; } return bbox ; } return { Object } public getCanvasBBox ( ) { let bbox = this . get ( CACHE_CANVAS_BBOX ) ; if ( ! bbox ) { bbox = this . calculateCanvasBBox ( ) ; this . set ( CACHE_CANVAS_BBOX , bbox ) ; } return bbox ; } /**
     * 将元素放到最前面
     */ public toFront ( ) { const group = this . get ( 'group' ) ; group . toFront ( ) ; } /**
     * 将元素放到最后面
     */ public toBack ( ) { const group = this . get ( 'group' ) ; group . toBack ( ) ; } /**
     * 显示元素
     */ public show ( ) { this . changeVisibility ( true ) ; } /**
     * 隐藏元素
     */ public hide ( ) { this . changeVisibility ( false ) ; } param { Boolean } visible public changeVisibility ( visible ) { const group = this . get ( 'group' ) ; if ( visible ) { group . show ( ) ; } else { group . hide ( ) ; } this . set ( 'visible' , visible ) ; } return { Boolean } public isVisible ( ) { return this . get ( 'visible' ) ; } param { Boolean } enable public enableCapture ( enable ) { const group = this . get ( 'group' ) ; if ( group ) { group . set ( 'capture' , enable ) ; } } public destroy ( ) { if ( ! this . destroyed ) { const animate = this . get ( 'animate' ) ; const group = this . get ( 'group' ) ; if ( animate ) { group . stopAnimate ( ) ; } group [ 'shapeMap' ] = { } ; this . clearCache ( ) ; group . remove ( ) ; ( this . _cfg as IItemBaseConfig | null ) = null ; this . destroyed = true ; } } }