2424import * as React from 'react' ;
2525import { AxisIdentifier , AxisMap , GraphContext , IHandlers } from './GraphContext' ;
2626
27+ export type origin = ( "upper-right" | "upper-left" | "upper-center" | "lower-right" | "lower-left" | "lower-center" )
28+
2729interface IProps {
28- // Specifies the upper left corner of the box (or other spots depending on origin)
29- x : number ,
30- y : number ,
31- usePixelPositioning ?: boolean ,
32- disallowSnapping ?: boolean ,
33- axis ?: AxisIdentifier ,
34- origin ?: "upper-right" | "upper-left" | "upper-center" | "lower-right" | "lower-left" | "lower-center" ,
35- // Specifies the offset of the pox from the origin point, In pixels
36- offset ?: number ,
37- // Dom ID of child, used for sizing of child
38- childId : string ,
39- opacity ?: number ,
40- // Function to move box
41- setPosition ?: ( x : number , y : number ) => void ,
42- onMouseMove ?: ( x : number , y : number ) => void
30+ /**
31+ * Specifies the X coordinate of the upper left corner of the box (or other spots depending on origin).
32+ */
33+ X : number ,
34+ /**
35+ * Specifies the Y coordinate of the upper left corner of the box (or other spots depending on origin).
36+ */
37+ Y : number ,
38+ /**
39+ * Determines if the positioning should be based on pixel values.
40+ */
41+ UsePixelPositioning ?: boolean ,
42+ /**
43+ * If true, snapping of the box to grid or other elements is disallowed.
44+ */
45+ DisallowSnapping ?: boolean ,
46+ /**
47+ * Identifier for the axis the box is associated with.
48+ */
49+ Axis ?: AxisIdentifier ,
50+ /**
51+ * Specifies the origin point for positioning the box.
52+ */
53+ Origin ?: origin ,
54+ /**
55+ * Specifies the offset of the box from the origin point, in pixels.
56+ */
57+ Offset ?: number ,
58+ /**
59+ * DOM ID of the child element, used for sizing the child.
60+ */
61+ ChildID : string ,
62+ /**
63+ * Opacity of the box.
64+ */
65+ Opacity ?: number ,
66+ /**
67+ * Function to set the position of the box.
68+ */
69+ SetPosition ?: ( x : number , y : number ) => void ,
70+ /**
71+ * Event handler for mouse move events.
72+ */
73+ OnMouseMove ?: ( x : number , y : number ) => void ,
74+ }
75+
76+ interface IGraphicProps {
77+ X : number ,
78+ Y : number ,
79+ Width : number ,
80+ Height : number ,
81+ Opacity ?: number
4382}
4483
84+ const offsetDefault = 0 ;
85+ const widthPadding = 3 ;
86+
4587const Infobox : React . FunctionComponent < IProps > = ( props ) => {
4688 const context = React . useContext ( GraphContext ) ;
4789 const [ isSelected , setSelected ] = React . useState < boolean > ( false ) ;
48- const [ position , setPosition ] = React . useState < { x : number , y : number } > ( { x : props . x , y : props . y } ) ;
49- const [ dimension , setDimensions ] = React . useState < { width : number , height : number } > ( { width : 100 , height : 100 } ) ;
90+ const [ position , setPosition ] = React . useState < { x : number , y : number } > ( { x : props . X , y : props . Y } ) ;
91+ const [ size , setSize ] = React . useState < { width : number , height : number } > ( { width : 100 , height : 100 } ) ;
5092 const [ guid , setGuid ] = React . useState < string > ( "" ) ;
51- const offsetDefault = 0 ;
52-
93+
5394 // Functions
5495 const calculateX = React . useCallback ( ( xArg : number ) => {
55- let x : number = ( props . usePixelPositioning ?? false ) ? context . XApplyPixelOffset ( xArg ) : context . XTransformation ( xArg ) ;
96+ let x : number = ( props . UsePixelPositioning ?? false ) ? context . XApplyPixelOffset ( xArg ) : context . XTransformation ( xArg ) ;
5697 // Convert x/y to upper-left corner
57- switch ( props . origin ) {
98+ switch ( props . Origin ) {
5899 case "lower-right" :
59100 case "upper-right" : {
60- x -= ( dimension . width + ( props . offset ?? offsetDefault ) ) ;
101+ x -= ( size . width + ( props . Offset ?? offsetDefault ) ) ;
61102 break ;
62103 }
63104 case "lower-center" :
64105 case "upper-center" : {
65- x -= Math . floor ( dimension . width / 2 ) ;
106+ x -= Math . floor ( size . width / 2 ) ;
66107 break ;
67108 }
68109 // Do-nothing case
69- case undefined :
110+ case undefined :
70111 case "lower-left" :
71112 case "upper-left" :
72- x += props . offset ?? offsetDefault ;
113+ x += props . Offset ?? offsetDefault ;
73114 break ;
74115 }
75116 return x ;
76- } , [ context . XApplyPixelOffset , context . XTransformation , props . origin , props . offset , props . usePixelPositioning , dimension ] ) ;
77-
117+ } , [ context . XApplyPixelOffset , context . XTransformation , props . Origin , props . Offset , props . UsePixelPositioning , size ] ) ;
118+
78119 const calculateY = React . useCallback ( ( yArg : number ) => {
79- let y : number = ( props . usePixelPositioning ?? false ) ? context . YApplyPixelOffset ( yArg ) : context . YTransformation ( yArg , AxisMap . get ( props . axis ) ) ;
120+ let y : number = ( props . UsePixelPositioning ?? false ) ? context . YApplyPixelOffset ( yArg ) : context . YTransformation ( yArg , AxisMap . get ( props . Axis ) ) ;
80121 // Convert x/y to upper-left corner
81- switch ( props . origin ) {
82- case undefined :
122+ switch ( props . Origin ) {
123+ case undefined :
83124 case "upper-left" :
84125 case "upper-right" :
85126 case "upper-center" :
86- y += props . offset ?? offsetDefault ;
127+ y += props . Offset ?? offsetDefault ;
87128 break ;
88129 case "lower-left" :
89130 case "lower-right" :
90131 case "lower-center" :
91- y -= ( dimension . height + ( props . offset ?? offsetDefault ) ) ;
132+ y -= ( size . height + ( props . Offset ?? offsetDefault ) ) ;
92133 break ;
93134 }
94135 return y ;
95- } , [ context . YApplyPixelOffset , context . YTransformation , props . origin , props . offset , props . usePixelPositioning , props . axis , dimension ] ) ;
96-
136+ } , [ context . YApplyPixelOffset , context . YTransformation , props . Origin , props . Offset , props . UsePixelPositioning , props . Axis , size ] ) ;
137+
97138 const onClick = React . useCallback ( ( xArg : number , yArg : number ) => {
98- const xP = calculateX ( props . x ) ;
139+ const xP = calculateX ( props . X ) ;
99140 const xT = context . XTransformation ( xArg ) ;
100- const yP = calculateY ( props . y ) ;
101- const yT = context . YTransformation ( yArg , AxisMap . get ( props . axis ) ) ;
102- if ( xT <= xP + dimension . width && xT >= xP && yT <= yP + dimension . height && yT >= yP ) {
141+ const yP = calculateY ( props . Y ) ;
142+ const yT = context . YTransformation ( yArg , AxisMap . get ( props . Axis ) ) ;
143+ if ( xT <= xP + size . width && xT >= xP && yT <= yP + size . height && yT >= yP ) {
103144 setSelected ( true ) ;
104145 }
105- } , [ props . x , props . y , calculateX , calculateY , dimension , setSelected , context . XTransformation , context . YTransformation , props . axis ] ) ;
106-
107- // Note: this is the only function not effected by usePixelPositioning
108- const onMove = props . onMouseMove === undefined ? undefined : React . useCallback ( ( xArg : number , yArg : number ) => {
109- if ( props . onMouseMove !== undefined ) props . onMouseMove ( xArg , yArg ) ;
110- } , [ props . onMouseMove ] ) ;
146+ } , [ props . X , props . Y , calculateX , calculateY , size , setSelected , context . XTransformation , context . YTransformation , props . Axis ] ) ;
111147
148+ // Note: this is the only function not effected by usePixelPositioning
149+ const onMove = props . OnMouseMove === undefined ? undefined : React . useCallback ( ( xArg : number , yArg : number ) => {
150+ if ( props . OnMouseMove !== undefined ) props . OnMouseMove ( xArg , yArg ) ;
151+ } , [ props . OnMouseMove ] ) ;
112152
113153 // useEffect
114154 React . useEffect ( ( ) => {
115155 const id = context . RegisterSelect ( {
116- axis : props . axis ,
156+ axis : props . Axis ,
117157 allowSnapping : false ,
118158 onRelease : ( _ ) => setSelected ( false ) ,
119159 onPlotLeave : ( _ ) => setSelected ( false ) ,
@@ -123,82 +163,82 @@ const Infobox: React.FunctionComponent<IProps> = (props) => {
123163 setGuid ( id )
124164 return ( ) => { context . RemoveSelect ( id ) }
125165 } , [ ] ) ;
126-
166+
127167 React . useEffect ( ( ) => {
128168 if ( guid === "" )
129169 return ;
130-
170+
131171 context . UpdateSelect ( guid , {
132- axis : props . axis ,
172+ axis : props . Axis ,
133173 allowSnapping : false ,
134174 onRelease : ( _ ) => setSelected ( false ) ,
135175 onPlotLeave : ( _ ) => setSelected ( false ) ,
136176 onClick,
137177 onMove
138178 } as IHandlers )
139- } , [ onClick , onMove , props . axis ] ) ;
140-
179+ } , [ onClick , onMove , props . Axis ] ) ;
180+
141181 React . useEffect ( ( ) => {
142- setPosition ( { x : props . x , y : props . y } ) ;
143- } , [ props . x , props . y ] ) ;
182+ setPosition ( { x : props . X , y : props . Y } ) ;
183+ } , [ props . X , props . Y ] ) ;
144184
145185 React . useEffect ( ( ) => {
146- if ( props . setPosition === undefined )
186+ if ( props . SetPosition === undefined )
147187 return ;
148- if ( ! isSelected && ( props . x !== position . x || props . y !== position . y ) )
149- props . setPosition ( position . x , position . y ) ;
188+ if ( ! isSelected && ( props . X !== position . x || props . Y !== position . y ) )
189+ props . SetPosition ( position . x , position . y ) ;
150190 } , [ isSelected , position ] ) ;
151-
191+
152192 React . useEffect ( ( ) => {
153193 if ( context . CurrentMode !== 'select' )
154194 setSelected ( false ) ;
155- } , [ context . CurrentMode ] ) ;
156-
195+ } , [ context . CurrentMode ] ) ;
196+
157197 React . useEffect ( ( ) => {
158- if ( isSelected && ! ( props . disallowSnapping ?? false ) )
159- setPosition ( { x : context . XHoverSnap , y : context . YHoverSnap [ AxisMap . get ( props . axis ) ] } ) ;
160- } , [ context . XHoverSnap , context . YHoverSnap , props . axis ] ) ;
161-
198+ if ( isSelected && ! ( props . DisallowSnapping ?? false ) )
199+ setPosition ( { x : context . XHoverSnap , y : context . YHoverSnap [ AxisMap . get ( props . Axis ) ] } ) ;
200+ } , [ context . XHoverSnap , context . YHoverSnap , props . Axis ] ) ;
201+
162202 React . useEffect ( ( ) => {
163- if ( isSelected && ( props . disallowSnapping ?? false ) )
164- setPosition ( { x : context . XHover , y : context . YHover [ AxisMap . get ( props . axis ) ] } ) ;
165- } , [ context . XHover , context . YHover , props . axis ] ) ;
166-
203+ if ( isSelected && ( props . DisallowSnapping ?? false ) )
204+ setPosition ( { x : context . XHover , y : context . YHover [ AxisMap . get ( props . Axis ) ] } ) ;
205+ } , [ context . XHover , context . YHover , props . Axis ] ) ;
206+
167207 // Get Heights and Widths
168208 React . useEffect ( ( ) => {
169- const domEle = document . getElementById ( props . childId ) ;
170- if ( domEle == null ) {
171- console . error ( `Invalid element id passed for child element in infobox ${ props . childId } ` ) ;
172- setDimensions ( { width : 100 , height : 100 } ) ;
173- return ;
174- }
175- if ( dimension . width === Math . ceil ( domEle . clientWidth ) && dimension . height === Math . ceil ( domEle . clientHeight ) ) return ;
176- setDimensions ( { width : Math . ceil ( domEle . clientWidth ) , height : Math . ceil ( domEle . clientHeight ) } ) ;
177- } , [ props . children , props . childId ] ) ;
209+ const newSize = getSize ( props . ChildID ) ;
210+ if ( newSize . width - widthPadding !== size . width || newSize . height !== size . height )
211+ setSize ( newSize ) ;
212+
213+ } , [ props . children , props . ChildID ] ) ;
178214
179215 return (
180216 < g >
181- < InfoGraphic x = { calculateX ( props . x ) } y = { calculateY ( props . y ) } width = { dimension . width } height = { dimension . height } opacity = { props . opacity } />
182- < foreignObject x = { calculateX ( props . x ) } y = { calculateY ( props . y ) } width = { dimension . width } height = { dimension . height } >
217+ < InfoGraphic X = { calculateX ( props . X ) } Y = { calculateY ( props . Y ) } Width = { size . width } Height = { size . height } Opacity = { props . Opacity } />
218+ < foreignObject x = { calculateX ( props . X ) } y = { calculateY ( props . Y ) } width = { size . width } height = { size . height } >
183219 { props . children }
184220 </ foreignObject >
185- { props . setPosition !== undefined && ( props . x !== position . x || props . y !== position . y ) ?
186- < InfoGraphic x = { calculateX ( position . x ) } y = { calculateY ( position . y ) } width = { dimension . width } height = { dimension . height } opacity = { props . opacity } />
221+ { props . SetPosition !== undefined && ( props . X !== position . x || props . Y !== position . y ) ?
222+ < InfoGraphic X = { calculateX ( position . x ) } Y = { calculateY ( position . y ) } Width = { size . width } Height = { size . height } Opacity = { props . Opacity } />
187223 : null }
188224 </ g > ) ;
189225}
190226
191- interface IGraphicProps {
192- x : number ,
193- y : number ,
194- width : number ,
195- height : number ,
196- opacity ?: number
197- }
198227const InfoGraphic : React . FunctionComponent < IGraphicProps > = ( props ) => {
199228 return (
200- < path d = { `M ${ props . x } ${ props . y } h ${ props . width } v ${ props . height } h -${ props . width } v -${ props . height } ` } stroke = { 'black' } style = { { opacity : props . opacity ?? 1 } } />
229+ < path d = { `M ${ props . X } ${ props . Y } h ${ props . Width } v ${ props . Height } h -${ props . Width } v -${ props . Height } ` } stroke = { 'black' } style = { { opacity : props . Opacity ?? 1 } } />
201230 ) ;
202231}
203232
233+ //helper functions
234+ const getSize = ( childID : string ) : { width : number , height : number } => {
235+ const childElement = document . getElementById ( childID ) ;
236+ if ( childElement == null ) {
237+ console . error ( `Invalid element id passed for child element in infobox ${ childID } ` ) ;
238+ return { width : 100 , height : 100 } ;
239+ }
240+ const width = Math . ceil ( childElement . clientWidth ) + widthPadding
241+ const height = Math . ceil ( childElement . clientHeight )
242+ return { width, height }
243+ }
204244export default Infobox ;
0 commit comments