@@ -23,6 +23,8 @@ class CoreGraph {
2323
2424 bendNode ;
2525
26+ gridSize = 20 ; // Configurable grid size in pixels
27+
2628 constructor ( id , element , dispatcher , superState , projectName , nodeValidator , edgeValidator , authorName ) {
2729 if ( dispatcher ) this . dispatcher = dispatcher ;
2830 if ( superState ) this . superState = superState ;
@@ -49,23 +51,54 @@ class CoreGraph {
4951 this . initizialize ( ) ;
5052 }
5153
54+ // Helper function to snap a value to the nearest grid point
55+ snapToGrid ( value ) {
56+ return Math . round ( value / this . gridSize ) * this . gridSize ;
57+ }
58+
59+ // Helper function to snap position to grid
60+ snapPositionToGrid ( position ) {
61+ return {
62+ x : this . snapToGrid ( position . x ) ,
63+ y : this . snapToGrid ( position . y ) ,
64+ } ;
65+ }
66+
67+ // Helper function to snap dimension to EVEN grid multiples
68+ // This ensures all borders lie on grid lines when center is on a grid point
69+ snapDimensionToGrid ( dimension ) {
70+ let gridCells = Math . round ( dimension / this . gridSize ) ;
71+ // Ensure at least 2 grid cells
72+ if ( gridCells < 2 ) {
73+ gridCells = 2 ;
74+ }
75+ // Ensure always an even number
76+ const evenCells = gridCells % 2 === 0 ? gridCells : gridCells + 1 ;
77+ return evenCells * this . gridSize ;
78+ }
79+
5280 initizialize ( ) {
5381 this . cy . nodeEditing ( {
5482 resizeToContentCueEnabled : ( ) => false ,
55- setWidth ( node , width ) {
83+ setWidth : ( node , width ) => {
84+ // Allow smooth resizing - don't snap during drag
5685 node . data ( 'style' , { ...node . data ( 'style' ) , width } ) ;
5786 } ,
58- setHeight ( node , height ) {
87+ setHeight : ( node , height ) => {
88+ // Allow smooth resizing - don't snap during drag
5989 node . data ( 'style' , { ...node . data ( 'style' ) , height } ) ;
6090 } ,
6191 isNoResizeMode ( node ) { return node . data ( 'type' ) !== 'ordin' ; } ,
6292 isNoControlsMode ( node ) { return node . data ( 'type' ) !== 'ordin' ; } ,
6393 } ) ;
6494
6595 this . cy . gridGuide ( {
66- snapToGridOnRelease : false ,
96+ snapToGridOnRelease : true ,
97+ snapToGridDuringDrag : true ,
6798 zoomDash : true ,
6899 panGrid : true ,
100+ gridSpacing : this . gridSize ,
101+ snapToAlignmentLocationOnRelease : true ,
69102 } ) ;
70103 this . cy . edgehandles ( {
71104 preview : false ,
@@ -164,12 +197,32 @@ class CoreGraph {
164197 } ) ;
165198 } ) ;
166199
200+ this . cy . on ( 'free' , 'node[type = "ordin"]' , ( e ) => {
201+ e . target . forEach ( ( node ) => {
202+ const currentPos = node . position ( ) ;
203+ const snappedPos = this . snapPositionToGrid ( currentPos ) ;
204+ node . position ( snappedPos ) ;
205+ } ) ;
206+ } ) ;
207+
167208 this . cy . on ( 'nodeediting.resizestart' , ( e , type , node ) => {
168209 node . scratch ( 'height' , node . data ( 'style' ) . height ) ;
169210 node . scratch ( 'width' , node . data ( 'style' ) . width ) ;
170211 node . scratch ( 'position' , { ...node . position ( ) } ) ;
171212 } ) ;
172213
214+ this . cy . on ( 'nodeediting.resizeend' , ( e , type , node ) => {
215+ // Snap dimensions to grid multiples
216+ const style = node . data ( 'style' ) || { } ;
217+ const snappedWidth = this . snapDimensionToGrid ( style . width || 100 ) ;
218+ const snappedHeight = this . snapDimensionToGrid ( style . height || 50 ) ;
219+ node . data ( 'style' , { ...style , width : snappedWidth , height : snappedHeight } ) ;
220+
221+ // Snap position to ensure all corners align to grid
222+ const snappedPos = this . snapPositionToGrid ( node . position ( ) ) ;
223+ node . position ( snappedPos ) ;
224+ } ) ;
225+
173226 this . cy . on ( 'hide-bend remove' , ( ) => {
174227 this . bendNode . removeListener ( 'drag grab dragfree' ) ; this . bendNode . addClass ( 'hidden' ) ;
175228 } ) ;
0 commit comments