fileOverview description import { IGroup , IShape , IElement , Point } from '@antv/g-base' ; import { deepMix , mix , each , isNil , isNumber , isArray } from '@antv/util' ; import { ILabelConfig , ShapeOptions } from '../interface/shape' ; import { EdgeConfig , EdgeData , IPoint , LabelStyle , ShapeStyle , Item , ModelConfig , UpdateType } from '../types' ; import { getLabelPosition , getLoopCfgs } from '../util/graphic' ; import { distance , getCircleCenterByPoints } from '../util/math' ; import { getControlPoint , getSpline } from '../util/path' ; import Global from '../global' ; import Shape from './shape' ; import { shapeBase , CLS_LABEL_BG_SUFFIX } from './shapeBase' ; const CLS_SHAPE = 'edge-shape' ; function revertAlign ( labelPosition ) { let textAlign = labelPosition ; if ( labelPosition === 'start' ) { textAlign = 'end' ; } else if ( labelPosition === 'end' ) { textAlign = 'start' ; } return textAlign ; } const singleEdge = { itemType : 'edge' , type { String } labelPosition : 'center' , type { Number } refX : 0 , type { Number } refY : 0 , type { Boolean } labelAutoRotate : false , options : { size : Global . defaultEdge . size , style : { x : 0 , y : 0 , stroke : Global . defaultEdge . style . stroke , lineAppendWidth : Global . defaultEdge . style . lineAppendWidth , } , labelCfg : { style : { fill : Global . edgeLabel . style . fill , fontSize : Global . edgeLabel . style . fontSize , fontFamily : Global . windowFontFamily } , } , stateStyles : { ... Global . edgeStateStyles , } , } , internal param { Array } points return { Array } getPath ( points ) { const path = [ ] ; each ( points , ( point , index ) => { if ( index === 0 ) { path . push ( [ 'M' , point . x , point . y ] ) ; } else { path . push ( [ 'L' , point . x , point . y ] ) ; } } ) ; return path ; } , getShapeStyle ( cfg ) { const { style : defaultStyle } = this . options as ModelConfig ; const strokeStyle = { stroke : cfg . color , } ; const style = mix ( { } , defaultStyle , strokeStyle , cfg . style ) ; const size = cfg . size || Global . defaultEdge . size ; cfg = this . getPathPoints ! ( cfg ) ; const { startPoint , endPoint } = cfg ; const controlPoints = this . getControlPoints ! ( cfg ) ; let points = [ startPoint ] ; if ( controlPoints ) { points = points . concat ( controlPoints ) ; } points . push ( endPoint ) ; const path = ( this as any ) . getPath ( points ) ; const styles = mix ( { } , Global . defaultEdge . style as ShapeStyle , { stroke : Global . defaultEdge . color , lineWidth : size , path , } as ShapeStyle , style ) ; return styles ; } , updateShapeStyle ( cfg , item , updateType ? ) { const group = item . getContainer ( ) ; const shape = item . getKeyShape ?. ( ) || group [ 'shapeMap' ] [ 'edge-shape' ] ; const { size } = cfg ; cfg = this . getPathPoints ! ( cfg ) ; const { startPoint , endPoint } = cfg ; const controlPoints = this . getControlPoints ! ( cfg ) ; let points = [ startPoint ] ; if ( controlPoints ) { points = points . concat ( controlPoints ) ; } points . push ( endPoint ) ; const currentAttr = shape . attr ( ) ; const previousStyle = cfg . style || { } ; if ( previousStyle . stroke === undefined ) { previousStyle . stroke = cfg . color ; } const source = cfg . sourceNode ; const target = cfg . targetNode ; let routeCfg = { radius : previousStyle . radius } ; if ( ! controlPoints ) { routeCfg = { source , target , offset : previousStyle . offset , radius : previousStyle . radius } ; } const path = ( this as any ) . getPath ( points , routeCfg ) ; let style = { } ; if ( updateType === 'move' ) { style = { path } ; } else { if ( currentAttr . endArrow && previousStyle . endArrow === false ) { cfg . style . endArrow = { path : '' , } ; } if ( currentAttr . startArrow && previousStyle . startArrow === false ) { cfg . style . startArrow = { path : '' , } ; } style = { ... cfg . style } ; if ( style . lineWidth === undefined ) style . lineWdith = ( isNumber ( size ) ? size : size ?. [ 0 ] ) || currentAttr . lineWidth ; if ( style . path === undefined ) style . path = path ; if ( style . stroke === undefined ) style . stroke = currentAttr . stroke || cfg . color ; } if ( shape ) { shape . attr ( style ) ; } } , getLabelStyleByPosition ( cfg , labelCfg , group ? ) { const labelPosition = labelCfg . position || this . labelPosition ; const style = { } ; const pathShape = group ?. [ 'shapeMap' ] [ CLS_SHAPE ] ; let pointPercent ; if ( labelPosition === 'start' ) { pointPercent = 0 ; } else if ( labelPosition === 'end' ) { pointPercent = 1 ; } else { pointPercent = 0.5 ; } const offsetX = labelCfg . refX || ( this . refX as number ) ; const offsetY = labelCfg . refY || ( this . refY as number ) ; if ( cfg . startPoint ! . x === cfg . endPoint ! . x && cfg . startPoint ! . y === cfg . endPoint ! . y ) { style . x = cfg . startPoint ! . x + offsetX ; style . y = cfg . startPoint ! . y + offsetY ; style . text = cfg . label as string ; return style ; } let autoRotate ; if ( isNil ( labelCfg . autoRotate ) ) autoRotate = this . labelAutoRotate ; else autoRotate = labelCfg . autoRotate ; const offsetStyle = getLabelPosition ( pathShape , pointPercent , offsetX , offsetY , autoRotate as boolean ) ; style . x = offsetStyle . x ; style . y = offsetStyle . y ; style . rotate = offsetStyle . rotate ; style . textAlign = this . _getTextAlign ! ( labelPosition as string , offsetStyle . angle as number ) ; style . text = cfg . label as string ; return style ; } , getLabelBgStyleByPosition ( label , labelCfg ? ) { if ( ! label ) { return { } ; } const bbox = label . getBBox ( ) ; const backgroundStyle = labelCfg . style && labelCfg . style . background ; if ( ! backgroundStyle ) { return { } ; } const { padding } = backgroundStyle ; const backgroundWidth = bbox . width + padding [ 1 ] + padding [ 3 ] ; const backgroundHeight = bbox . height + padding [ 0 ] + padding [ 2 ] ; const style = { ... backgroundStyle , width : backgroundWidth , height : backgroundHeight , x : bbox . minX - padding [ 3 ] , y : bbox . minY - padding [ 0 ] , matrix : [ 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 ] } ; let autoRotate ; if ( isNil ( labelCfg . autoRotate ) ) autoRotate = this . labelAutoRotate ; else autoRotate = labelCfg . autoRotate ; if ( autoRotate ) { style . matrix = label . attr ( 'matrix' ) || [ 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 ] ; } return style ; } , _getTextAlign ( labelPosition , angle ) { let textAlign = 'center' ; if ( ! angle ) { return labelPosition ; } angle = angle % ( Math . PI * 2 ) ; if ( labelPosition !== 'center' ) { if ( ( angle >= 0 && angle <= Math . PI / 2 ) || ( angle >= ( 3 / 2 ) * Math . PI && angle < 2 * Math . PI ) ) { textAlign = labelPosition ; } else { textAlign = revertAlign ( labelPosition ) ; } } return textAlign ; } , internal param { Object } cfg return { Array } getControlPoints ( cfg ) { return cfg . controlPoints ; } , internal param { Object } cfg return { Object } getPathPoints ( cfg ) { return cfg ; } , @override
     *  param { Object } cfg param { G . Group } group return { IShape } drawShape ( cfg , group ) { const shapeStyle = this . getShapeStyle ! ( cfg ) ; const shape = group . addShape ( 'path' , { className : CLS_SHAPE , name : CLS_SHAPE , attrs : shapeStyle , } ) ; group [ 'shapeMap' ] [ CLS_SHAPE ] = shape ; return shape ; } , drawLabel ( cfg , group ) { const { labelCfg : defaultLabelCfg } = this . options as ModelConfig ; const labelCfg = deepMix ( { } , defaultLabelCfg , cfg . labelCfg ) ; const labelStyle = this . getLabelStyle ! ( cfg , labelCfg , group ) ; const rotate = labelStyle . rotate ; delete labelStyle . rotate ; const label = group . addShape ( 'text' , { attrs : labelStyle , name : 'text-shape' , labelRelated : true } ) ; group [ 'shapeMap' ] [ 'text-shape' ] = label ; if ( ! isNaN ( rotate ) && rotate !== '' ) { label . rotateAtStart ( rotate ) ; } if ( labelStyle . background ) { const rect = this . drawLabelBg ( cfg , group , label , labelStyle , rotate ) ; const labelBgClassname = this . itemType + CLS_LABEL_BG_SUFFIX ; rect . set ( 'classname' , labelBgClassname ) ; group [ 'shapeMap' ] [ labelBgClassname ] = rect ; label . toFront ( ) ; } return label ; } , drawLabelBg ( cfg , group , label , labelStyle , rotate ) { const { labelCfg : defaultLabelCfg } = this . options as ModelConfig ; const labelCfg = deepMix ( { } , defaultLabelCfg , cfg . labelCfg ) ; const style = this . getLabelBgStyleByPosition ( label , labelCfg ) ; const rect = group . addShape ( 'rect' , { name : 'text-bg-shape' , attrs : style , labelRelated : true } ) ; group [ 'shapeMap' ] [ 'text-bg-shape' ] = rect ; return rect ; } , } ; const singleEdgeDef = { ... shapeBase , ... singleEdge } ; Shape . registerEdge ( 'single-edge' , singleEdgeDef ) ; Shape . registerEdge ( 'line' , { getControlPoints ( ) { return undefined ; } , } , 'single-edge' ) ; Shape . registerEdge ( 'spline' , { getPath ( points ) { const path = getSpline ( points ) ; return path ; } , } , 'single-edge' ) ; Shape . registerEdge ( 'arc' , { curveOffset : 20 , clockwise : 1 , getControlPoints ( cfg ) { const { startPoint , endPoint } = cfg ; const midPoint = { x : ( startPoint . x + endPoint . x ) / 2 , y : ( startPoint . y + endPoint . y ) / 2 , } ; let center ; let arcPoint ; if ( cfg . controlPoints !== undefined ) { arcPoint = cfg . controlPoints [ 0 ] ; center = getCircleCenterByPoints ( startPoint , arcPoint , endPoint ) ; if ( startPoint . x <= endPoint . x && startPoint . y > endPoint . y ) { this . clockwise = center . x > arcPoint . x ? 0 : 1 ; } else if ( startPoint . x <= endPoint . x && startPoint . y < endPoint . y ) { this . clockwise = center . x > arcPoint . x ? 1 : 0 ; } else if ( startPoint . x > endPoint . x && startPoint . y <= endPoint . y ) { this . clockwise = center . y < arcPoint . y ? 0 : 1 ; } else { this . clockwise = center . y < arcPoint . y ? 1 : 0 ; } if ( ( arcPoint . x - startPoint . x ) / ( arcPoint . y - startPoint . y ) === ( endPoint . x - startPoint . x ) / ( endPoint . y - startPoint . y ) ) { return [ ] ; } } else { if ( cfg . curveOffset === undefined ) { cfg . curveOffset = this . curveOffset ; } if ( isArray ( cfg . curveOffset ) ) { cfg . curveOffset = cfg . curveOffset [ 0 ] ; } if ( cfg . curveOffset < 0 ) { this . clockwise = 0 ; } else { this . clockwise = 1 ; } const vec = { x : endPoint . x - startPoint . x , y : endPoint . y - startPoint . y , } ; const edgeAngle = Math . atan2 ( vec . y , vec . x ) ; arcPoint = { x : cfg . curveOffset * Math . cos ( - Math . PI / 2 + edgeAngle ) + midPoint . x , y : cfg . curveOffset * Math . sin ( - Math . PI / 2 + edgeAngle ) + midPoint . y , } ; center = getCircleCenterByPoints ( startPoint , arcPoint , endPoint ) ; } const radius = distance ( startPoint , center ) ; const controlPoints = [ { x : radius , y : radius } ] ; return controlPoints ; } , getPath ( points ) { const path = [ ] ; path . push ( [ 'M' , points [ 0 ] . x , points [ 0 ] . y ] ) ; if ( points . length === 2 ) { path . push ( [ 'L' , points [ 1 ] . x , points [ 1 ] . y ] ) ; } else { path . push ( [ 'A' , points [ 1 ] . x , points [ 1 ] . y , 0 , 0 , this . clockwise as number , points [ 2 ] . x , points [ 2 ] . y , ] ) ; } return path ; } , } , 'single-edge' ) ; Shape . registerEdge ( 'quadratic' , { curvePosition : 0.5 , curveOffset : - 20 , getControlPoints ( cfg ) { let { controlPoints } = cfg ; if ( ! controlPoints || ! controlPoints . length ) { const { startPoint , endPoint } = cfg ; if ( cfg . curveOffset === undefined ) cfg . curveOffset = this . curveOffset ; if ( cfg . curvePosition === undefined ) cfg . curvePosition = this . curvePosition ; if ( isArray ( this . curveOffset ) ) cfg . curveOffset = cfg . curveOffset [ 0 ] ; if ( isArray ( this . curvePosition ) ) cfg . curvePosition = cfg . curveOffset [ 0 ] ; const innerPoint = getControlPoint ( startPoint as Point , endPoint as Point , cfg . curvePosition as number , cfg . curveOffset as number ) ; controlPoints = [ innerPoint ] ; } return controlPoints ; } , getPath ( points ) { const path = [ ] ; path . push ( [ 'M' , points [ 0 ] . x , points [ 0 ] . y ] ) ; path . push ( [ 'Q' , points [ 1 ] . x , points [ 1 ] . y , points [ 2 ] . x , points [ 2 ] . y ] ) ; return path ; } , } , 'single-edge' ) ; Shape . registerEdge ( 'cubic' , { curvePosition : [ 1 / 2 , 1 / 2 ] , curveOffset : [ - 20 , 20 ] , getControlPoints ( cfg ) { let { controlPoints } = cfg ; if ( cfg . curveOffset === undefined ) cfg . curveOffset = this . curveOffset ; if ( cfg . curvePosition === undefined ) cfg . curvePosition = this . curvePosition ; if ( isNumber ( cfg . curveOffset ) ) cfg . curveOffset = [ cfg . curveOffset , - cfg . curveOffset ] ; if ( isNumber ( cfg . curvePosition ) ) cfg . curvePosition = [ cfg . curvePosition , 1 - cfg . curvePosition ] ; if ( ! controlPoints || ! controlPoints . length || controlPoints . length < 2 ) { const { startPoint , endPoint } = cfg ; const innerPoint1 = getControlPoint ( startPoint as Point , endPoint as Point , cfg . curvePosition [ 0 ] , cfg . curveOffset [ 0 ] ) ; const innerPoint2 = getControlPoint ( startPoint as Point , endPoint as Point , cfg . curvePosition [ 1 ] , cfg . curveOffset [ 1 ] ) ; controlPoints = [ innerPoint1 , innerPoint2 ] ; } return controlPoints ; } , getPath ( points ) { const path = [ ] ; path . push ( [ 'M' , points [ 0 ] . x , points [ 0 ] . y ] ) ; path . push ( [ 'C' , points [ 1 ] . x , points [ 1 ] . y , points [ 2 ] . x , points [ 2 ] . y , points [ 3 ] . x , points [ 3 ] . y , ] ) ; return path ; } , } , 'single-edge' ) ; Shape . registerEdge ( 'cubic-vertical' , { curvePosition : [ 1 / 2 , 1 / 2 ] , minCurveOffset : [ 0 , 0 ] , curveOffset : undefined , getControlPoints ( cfg ) { const { startPoint , endPoint } = cfg ; if ( cfg . curvePosition === undefined ) cfg . curvePosition = this . curvePosition ; if ( cfg . curveOffset === undefined ) cfg . curveOffset = this . curveOffset ; if ( cfg . minCurveOffset === undefined ) cfg . minCurveOffset = this . minCurveOffset ; if ( isNumber ( cfg . curveOffset ) ) cfg . curveOffset = [ cfg . curveOffset , - cfg . curveOffset ] ; if ( isNumber ( cfg . minCurveOffset ) ) cfg . minCurveOffset = [ cfg . minCurveOffset , - cfg . minCurveOffset ] ; if ( isNumber ( cfg . curvePosition ) ) cfg . curvePosition = [ cfg . curvePosition , 1 - cfg . curvePosition ] ; const yDist = endPoint ! . y - startPoint ! . y ; let curveOffset = [ 0 , 0 ] ; if ( cfg . curveOffset ) { curveOffset = cfg . curveOffset ; } else if ( Math . abs ( yDist ) < Math . abs ( cfg . minCurveOffset [ 0 ] ) ) { curveOffset = cfg . minCurveOffset ; } const innerPoint1 = { x : startPoint ! . x , y : startPoint ! . y + yDist * ( this as any ) . curvePosition [ 0 ] + curveOffset [ 0 ] , } ; const innerPoint2 = { x : endPoint ! . x , y : endPoint ! . y - yDist * ( this as any ) . curvePosition [ 1 ] + curveOffset [ 1 ] , } ; return [ innerPoint1 , innerPoint2 ] ; } , } , 'cubic' ) ; Shape . registerEdge ( 'cubic-horizontal' , { curvePosition : [ 1 / 2 , 1 / 2 ] , minCurveOffset : [ 0 , 0 ] , curveOffset : undefined , getControlPoints ( cfg ) { const { startPoint , endPoint } = cfg ; if ( cfg . curvePosition === undefined ) cfg . curvePosition = this . curvePosition ; if ( cfg . curveOffset === undefined ) cfg . curveOffset = this . curveOffset ; if ( cfg . minCurveOffset === undefined ) cfg . minCurveOffset = this . minCurveOffset ; if ( isNumber ( cfg . curveOffset ) ) cfg . curveOffset = [ cfg . curveOffset , - cfg . curveOffset ] ; if ( isNumber ( cfg . minCurveOffset ) ) cfg . minCurveOffset = [ cfg . minCurveOffset , - cfg . minCurveOffset ] ; if ( isNumber ( cfg . curvePosition ) ) cfg . curvePosition = [ cfg . curvePosition , 1 - cfg . curvePosition ] ; const xDist = endPoint ! . x - startPoint ! . x ; let curveOffset = [ 0 , 0 ] ; if ( cfg . curveOffset ) { curveOffset = cfg . curveOffset ; } else if ( Math . abs ( xDist ) < Math . abs ( cfg . minCurveOffset [ 0 ] ) ) { curveOffset = cfg . minCurveOffset ; } const innerPoint1 = { x : startPoint ! . x + xDist * ( this as any ) . curvePosition [ 0 ] + curveOffset [ 0 ] , y : startPoint ! . y , } ; const innerPoint2 = { x : endPoint ! . x - xDist * ( this as any ) . curvePosition [ 1 ] + curveOffset [ 1 ] , y : endPoint ! . y , } ; const controlPoints = [ innerPoint1 , innerPoint2 ] ; return controlPoints ; } , } , 'cubic' ) ; Shape . registerEdge ( 'loop' , { getPathPoints ( cfg ) { return getLoopCfgs ( cfg as EdgeData ) ; } , getControlPoints ( cfg ) { return cfg . controlPoints ; } , afterDraw ( cfg ) { cfg . controlPoints = undefined ; } , afterUpdate ( cfg ) { cfg . controlPoints = undefined ; } , } , 'cubic' ) ;