import { path } from "d3-path" ; import { range } from "d3-array" ; import { Group } from "../scene/group" ; import { Cubicon } from "./cubicon" ; import { rToD , xGtoW , yGtoW , xWtoG } from "../math/convertUnit" ; import { Vector2 } from "../math/vector" ; import { COLOR , PT_TO_SIDES_DATA , RECT_GRID_DATA } from "./constants" ; import { curveNatural , line } from "d3" ; /**
 * Configuration structure of basic shapes (Rectangle, Square and Circle).
 *
 * If this is not specified, then fall back to all of the default values.
 */ export interface SHAPE_CONFIG { default fillColor ? : string ; default fillOpacity ? : number ; default strokeColor ? : string ; default strokeWidth ? : number ; } export const SHAPE_DEFAULT_CONFIG = { fillColor : "none" , fillOpacity : 1 , strokeColor : "#fff" , strokeWidth : 2 , } ; /**
 * Configuration structure of line-like shapes (Line and Vector).
 *
 * If this is not specified, then fall back to all of the default values.
 */ export interface LINE_CONFIG { default lineColor ? : string ; default lineWidth ? : number ; } export const LINE_DEFAULT_CONFIG = { lineColor : "#fff" , lineWidth : 2 , } ; /**
 * Base class of all geometric cubicon shape.
 *
 * Child classes: Rectangle, Square, Circle, Line, Vector (shape).
 */ export abstract class Geometry extends Cubicon { readonly cubType = "geometry" ; abstract readonly geoType : string ; fillColor : any ; fillOpacity : any ; strokeColor : any ; strokeWidth : any ; lineColor : any ; lineWidth : any ; constructor ( { group , position = new Vector2 ( 0 , 0 ) , } ) { super ( { group : group , position : position } ) ; this . group = group ; } coordsGtoW ( point ) { return new Vector2 ( xGtoW ( point . x ) , yGtoW ( point . y ) ) ; } } /**
 * Return the barebone of a rectangle shape.
 */ export class Rectangle extends Geometry { /**
     * Geometry type of the rectangle.
     */ readonly geoType = "rectangle" ; /**
     * Width of the rectangle (in grid coordinate system).
     */ readonly width : number ; internal readonly height : number ; param params constructor ( params ) { super ( { group : params . group , position : params . position } ) ; this . width = params . width ; this . height = params . height ; ( { fillColor : this . fillColor = SHAPE_DEFAULT_CONFIG . fillColor , fillOpacity : this . fillOpacity = SHAPE_DEFAULT_CONFIG . fillOpacity , strokeColor : this . strokeColor = SHAPE_DEFAULT_CONFIG . strokeColor , strokeWidth : this . strokeWidth = SHAPE_DEFAULT_CONFIG . strokeWidth , } = params . CONFIG ?? SHAPE_DEFAULT_CONFIG ) ; } /**
     * Add the shape of this rectangle onto SVG.
     */ render ( ) { this . checkIfRendered ( ) ; this . isRendered = true ; this . g_cubiconWrapper = this . svg_group . append ( "g" ) . attr ( "class" , `rectangle-wrapper` ) . style ( "transform-box" , "fill-box" ) . style ( "transform-origin" , `center` ) ; const path = this . definePath ( ) ; this . def_cubiconBase = this . g_cubiconWrapper . append ( "path" ) ; this . def_cubiconBase . attr ( "class" , "rectangle" ) . attr ( "d" , path . toString ( ) ) . attr ( "stroke" , this . strokeColor ) . attr ( "stroke-width" , this . strokeWidth ) . attr ( "fill" , this . fillColor ) . attr ( "fill-opacity" , this . fillOpacity ) ; this . def_cubiconBase . style ( "transform-box" , "fill-box" ) . style ( "transform-origin" , "center" ) ; return this ; } /**
     * Draw (not render) a rectangular stroke path.
     */ private definePath ( ) { const Wposition = this . coordsGtoW ( this . position ) ; const Wwidth = xGtoW ( this . width ) ; const Wheight = xGtoW ( this . height ) ; const X = - Wwidth / 2 + Wposition . x ; const Y = Wheight / 2 + Wposition . y ; const rectPath = path ( ) ; rectPath . moveTo ( X , Y ) ; rectPath . lineTo ( X + Wwidth , Y ) ; rectPath . lineTo ( X + Wwidth , Y - Wheight ) ; rectPath . lineTo ( X , Y - Wheight ) ; rectPath . lineTo ( X , Y + ( this . strokeWidth ?? 0 ) / 2 ) ; return rectPath ; } param ptPositions param direction returns pointToSides ( ptPositions , direction ) { const g_pointToSides = this . g_cubiconWrapper . append ( "g" ) ; const Wposition = this . coordsGtoW ( this . position ) ; g_pointToSides . attr ( "class" , "lines-to-side" ) . attr ( "transform" , `translate(${ Wposition . x }, ${ Wposition . y })` ) ; const horizontalLines = [ ] ; const verticalLines = [ ] ; const generalParams = { group : this . group , CONFIG : { lineColor : this . strokeColor , lineWidth : this . strokeWidth , } , } ; const endPoint_x = ( ( direction [ 0 ] >= 0 ? 1 : - 1 ) * this . width ) / 2 ; ptPositions . forEach ( ( p ) => { horizontalLines . push ( new Line ( { ... generalParams , startPoint : new Vector2 ( p . x + ( p . x >= endPoint_x ? 1 : - 1 ) * xWtoG ( this . strokeWidth / 2 ) , p . y ) , endPoint : new Vector2 ( endPoint_x , p . y ) , } ) . render ( ) . setParentHTMLTag ( g_pointToSides ) ) ; verticalLines . push ( new Line ( { ... generalParams , startPoint : p , endPoint : new Vector2 ( p . x , ( ( direction [ 1 ] >= 0 ? 1 : - 1 ) * this . height ) / 2 ) , } ) . render ( ) . setParentHTMLTag ( g_pointToSides ) ) ; } ) ; return { cubicon : this , horizontalLines : horizontalLines , verticalLines : verticalLines , } ; } returns drawInnerGrid ( ) { const g_drawInnerGrid = this . g_cubiconWrapper . append ( "g" ) ; const Wposition = this . coordsGtoW ( this . position ) ; g_drawInnerGrid . attr ( "class" , "rect-inner-grid" ) . attr ( "transform" , `translate(${ Wposition . x }, ${ Wposition . y })` ) ; const generalParams = { group : this . group , CONFIG : { lineColor : COLOR . BLUE_1 , lineWidth : 1 , } , } ; const horizontalLines = [ ] ; for ( let i of range ( - this . height / 2 + 1 , this . height / 2 , 1 ) ) { horizontalLines . push ( new Line ( { ... generalParams , startPoint : new Vector2 ( - this . width / 2 , i ) , endPoint : new Vector2 ( this . width / 2 , i ) , } ) . render ( ) . setParentHTMLTag ( g_drawInnerGrid ) ) ; } const verticalLines = [ ] ; for ( let i of range ( - this . width / 2 + 1 , this . width / 2 , 1 ) ) { verticalLines . push ( new Line ( { ... generalParams , startPoint : new Vector2 ( i , - this . height / 2 ) , endPoint : new Vector2 ( i , this . height / 2 ) , } ) . render ( ) . setParentHTMLTag ( g_drawInnerGrid ) ) ; } return { cubicon : this , horizontalLines : horizontalLines , verticalLines : verticalLines , } ; } } /**
 * Return the barebone of a square shape.
 */ export class Square extends Rectangle { param params constructor ( params ) { super ( { group : params . group , position : params . position , width : params . sideLength , height : params . sideLength , CONFIG : params . CONFIG , } ) ; } } /**
 * Return the barebone of a circle shape.
 */ export class Circle extends Geometry { /**
     * Geometry type of the circle.
     */ readonly geoType = "circle" ; /**
     * Radius of the rectangle (in grid coordinate system).
     */ readonly radius : number ; param params constructor ( params ) { super ( { group : params . group , position : params . position } ) ; this . radius = params . radius ; ( { fillColor : this . fillColor = SHAPE_DEFAULT_CONFIG . fillColor , fillOpacity : this . fillOpacity = SHAPE_DEFAULT_CONFIG . fillOpacity , strokeColor : this . strokeColor = SHAPE_DEFAULT_CONFIG . strokeColor , strokeWidth : this . strokeWidth = SHAPE_DEFAULT_CONFIG . strokeWidth , } = params . CONFIG ?? SHAPE_DEFAULT_CONFIG ) ; } /**
     * Draw (and render) the shape of this circle onto SVG.
     */ render ( ) { this . checkIfRendered ( ) ; this . isRendered = true ; this . g_cubiconWrapper = this . svg_group . append ( "g" ) . attr ( "class" , `circle-wrapper` ) . style ( "transform-box" , "fill-box" ) . style ( "transform-origin" , `center` ) ; const Wposition = this . coordsGtoW ( this . position ) ; const Wradius = xGtoW ( this . radius ) ; this . applyToHTMLFlow ( this . g_cubiconWrapper , Wposition , Wradius ) ; this . def_cubiconBase . attr ( "class" , "circle" ) ; return this ; } protected applyToHTMLFlow ( g_cubiconWrapper , Wposition , Wradius ) { this . def_cubiconBase = g_cubiconWrapper . append ( "circle" ) . attr ( "cx" , Wposition . x ) . attr ( "cy" , Wposition . y ) . attr ( "r" , Wradius ) . attr ( "fill" , this . fillColor ) . attr ( "fill-opacity" , this . fillOpacity ) . attr ( "stroke" , this . strokeColor ) . attr ( "stroke-width" , this . strokeWidth ) ; this . def_cubiconBase . style ( "transform-box" , "fill-box" ) . style ( "transform-origin" , "center" ) ; } } export class GridOrigin extends Circle { constructor ( group ) { super ( { group : group , radius : xWtoG ( 2.2 ) , } ) ; } } /**
 * Return the barebone of a line shape.
 */ export class Line extends Geometry { /**
     * Geometry type of the line.
     */ readonly geoType = "line" ; /**
     * Start point (tail) of the line.
     */ readonly startPoint : Vector2 ; /**
     * End point (head) of the line.
     */ readonly endPoint : Vector2 ; protected parentGroupTag = this . svg_group ; constructor ( params ) { super ( { group : params . group , position : params . startPoint } ) ; this . startPoint = typeof params . startPoint !== "undefined" ? new Vector2 ( params . startPoint . x , params . startPoint . y ) : new Vector2 ( 0 , 0 ) ; this . endPoint = new Vector2 ( params . endPoint . x , params . endPoint . y ) ; ( { lineColor : this . lineColor = LINE_DEFAULT_CONFIG . lineColor , lineWidth : this . lineWidth = LINE_DEFAULT_CONFIG . lineWidth , } = params . CONFIG ?? LINE_DEFAULT_CONFIG ) ; } /**
     * Draw (and render) the shape of this line onto SVG.
     */ render ( ) { this . checkIfRendered ( ) ; this . isRendered = true ; const WstartPoint = this . coordsGtoW ( this . startPoint ) ; const WendPoint = this . coordsGtoW ( this . endPoint ) ; this . applyToHTMLFlow ( this . parentGroupTag , WstartPoint , WendPoint ) ; this . g_cubiconWrapper . attr ( "class" , `line-wrapper` ) ; this . def_cubiconBase . attr ( "class" , "line" ) ; return this ; } protected applyToHTMLFlow ( parentGroupTag , WstartPoint , WendPoint ) { this . g_cubiconWrapper = parentGroupTag . append ( "g" ) . style ( "transform-box" , "fill-box" ) . style ( "transform-origin" , `center` ) ; this . def_cubiconBase = this . g_cubiconWrapper . append ( "line" ) . attr ( "x1" , WstartPoint . x ) . attr ( "y1" , WstartPoint . y ) . attr ( "x2" , WendPoint . x ) . attr ( "y2" , WendPoint . y ) . attr ( "stroke" , this . lineColor ) . attr ( "stroke-width" , this . lineWidth ) ; } getWpoint ( point ) { return this . coordsGtoW ( point ) ; } setParentHTMLTag ( parentGroupTag ) { this . g_cubiconWrapper . remove ( ) ; this . parentGroupTag = parentGroupTag ; this . isRendered = false ; this . render ( ) ; return this ; } setParent ( parent ) { this . g_cubiconWrapper . remove ( ) ; this . parentGroupTag = parent . g_cubiconWrapper ; this . render ( ) ; return this ; } } export class Vector extends Geometry { /**
     * Geometry type of the vector.
     */ readonly geoType = "vector" ; /**
     * Start point (tail) of the vector.
     */ readonly startPoint : Vector2 ; /**
     * End point (head) of the vector.
     */ readonly endPoint : Vector2 ; /**
     * The smaller angle (in degree) between the vector and the x axis.
     */ theta : number ; def_lineStroke : any ; def_arrowHead : any ; constructor ( params ) { super ( { group : params . group , position : params . startPoint , } ) ; this . startPoint = typeof params . startPoint !== "undefined" ? params . startPoint : new Vector2 ( 0 , 0 ) ; this . endPoint = params . endPoint ; ( { lineColor : this . lineColor = LINE_DEFAULT_CONFIG . lineColor , lineWidth : this . lineWidth = LINE_DEFAULT_CONFIG . lineWidth , } = params . CONFIG ?? LINE_DEFAULT_CONFIG ) ; this . theta = rToD ( Math . atan2 ( this . endPoint . y - this . startPoint . y , this . endPoint . x - this . startPoint . x ) ) ; } getWpoint ( point ) { return this . coordsGtoW ( point ) ; } render ( ) { this . checkIfRendered ( ) ; this . isRendered = true ; const WstartPoint = this . coordsGtoW ( this . startPoint ) ; this . g_cubiconWrapper = this . svg_group . append ( "g" ) . attr ( "class" , `vector-wrapper` ) . attr ( "transform-box" , "fill-box" ) . attr ( "transform-origin" , `${ WstartPoint . x } ${ WstartPoint . y }` ) ; this . def_cubiconBase = this . g_cubiconWrapper . append ( "g" ) . attr ( "class" , "vector-group" ) ; this . drawVectorLine ( ) ; this . drawVectorArrowHead ( ) ; return this ; } private drawVectorLine ( ) { const WstartPoint = this . coordsGtoW ( this . startPoint ) ; const WendPoint = this . coordsGtoW ( this . endPoint ) ; this . def_lineStroke = this . def_cubiconBase . append ( "line" ) . attr ( "class" , "vector-line" ) . attr ( "x1" , WstartPoint . x ) . attr ( "y1" , WstartPoint . y ) . attr ( "x2" , WendPoint . x ) . attr ( "y2" , WendPoint . y ) . attr ( "stroke" , this . lineColor ) . attr ( "stroke-width" , this . lineWidth ) ; } private drawVectorArrowHead ( ) { const WendPoint = this . coordsGtoW ( this . endPoint ) ; const headWidth = 0.3 ; const headHeight = 0.5 ; this . def_arrowHead = this . def_cubiconBase . append ( "polygon" ) . attr ( "class" , "vector-arrow-head" ) . attr ( "points" , `${ xGtoW ( - headWidth ) }, ${ yGtoW ( - headHeight ) } 0, 0 ${ xGtoW ( headWidth ) }, ${ yGtoW ( - headHeight ) }` ) . attr ( "fill" , this . lineColor ) . attr ( "stroke" , "none" ) . attr ( "transform" , `translate(${ WendPoint . x } ${ WendPoint . y }) rotate(${ this . theta - 90 })` ) ; } } /**
 * Generate a 2d curve.
 */ export class ParamatricCurve extends Geometry { readonly geoType = "parametric-curve" ; points : Vector2 [ ] ; functionDef : ( t : number ) => Vector2 ; tRange : [ number , number ] ; constructor ( params ) { super ( { group : params . group } ) ; this . points = range ( params . tRange [ 0 ] , params . tRange [ 1 ] + ( params . dt || 0.02 ) , params . dt || 0.02 ) . map ( ( t ) => params . functionDef ( t ) ) ; this . functionDef = params . functionDef ; this . tRange = params . tRange ; ( { lineColor : this . lineColor = LINE_DEFAULT_CONFIG . lineColor , lineWidth : this . lineWidth = LINE_DEFAULT_CONFIG . lineWidth , } = params . CONFIG ?? LINE_DEFAULT_CONFIG ) ; } render ( ) { this . checkIfRendered ( ) ; this . isRendered = true ; this . g_cubiconWrapper = this . svg_group . append ( "g" ) . attr ( "class" , `arc-wrapper` ) . style ( "transform-box" , "fill-box" ) . style ( "transform-origin" , `center` ) ; this . def_cubiconBase = this . g_cubiconWrapper . append ( "path" ) . attr ( "class" , "parametric-curve" ) . attr ( "d" , this . getData ( ) ) . attr ( "fill" , "none" ) . attr ( "stroke" , this . lineColor ) . attr ( "stroke-width" , this . lineWidth ) ; return this ; } private getData ( ) { const points = this . points . map ( ( pt ) => [ pt . x , pt . y , ] ) ; const curveGenerator = line ( ) . curve ( curveNatural ) . x ( ( d ) => xGtoW ( d [ 0 ] ) ) . y ( ( d ) => yGtoW ( d [ 1 ] ) ) ; return curveGenerator ( points ) ; } getOutputVector ( t ) { return this . functionDef ( t ) ; } getFirstOutputPoint ( ) { return this . functionDef ( this . tRange [ 0 ] ) ; } getLastOutputPoint ( ) { return this . functionDef ( this . tRange [ 1 ] ) ; } }