@@ -14,15 +14,15 @@ import { changeView, changeSlider, setCurrentTabInApp } from '../../slices/mainS
14
14
*/
15
15
16
16
const defaultMargin : DefaultMargin = {
17
- top : 30 ,
17
+ top : 60 ,
18
18
left : 30 ,
19
19
right : 55 ,
20
20
bottom : 70 ,
21
21
} ;
22
22
23
23
// Fixed node separation distances
24
- const FIXED_NODE_HEIGHT = 100 ; // Vertical distance between nodes
25
- const FIXED_NODE_WIDTH = 180 ; // Horizontal distance between nodes
24
+ const FIXED_NODE_HEIGHT = 120 ; // Vertical distance between nodes
25
+ const FIXED_NODE_WIDTH = 220 ; // Horizontal distance between nodes
26
26
27
27
// main function exported to StateRoute
28
28
// below we destructure the props
@@ -117,6 +117,8 @@ function History(props: Record<string, unknown>): JSX.Element {
117
117
return changedState ; // return the changedState array with any and all stateful delta objects.
118
118
}
119
119
120
+ if ( index === 0 ) return '' ; // Return empty string for initial state
121
+
120
122
const delta = diff (
121
123
// 'diff' function from 'jsondiffpatch' returns the difference in state between the (snapshot that occurred before the indexed snapshot) and the (indexed snapshot).
122
124
statelessCleaning ( snapshots [ index - 1 ] ) ,
@@ -135,13 +137,9 @@ function History(props: Record<string, unknown>): JSX.Element {
135
137
const svg = d3 . select ( svgRef . current ) ;
136
138
svg . selectAll ( '*' ) . remove ( ) ;
137
139
138
- // Create tree layout with fixed node separation
139
- const treeLayout = d3 . tree ( ) . nodeSize ( [ FIXED_NODE_WIDTH , FIXED_NODE_HEIGHT ] ) ; // Set fixed sizes between nodes
140
-
141
- // Calculate the tree structure
140
+ const treeLayout = d3 . tree ( ) . nodeSize ( [ FIXED_NODE_WIDTH , FIXED_NODE_HEIGHT ] ) ;
142
141
const d3root = treeLayout ( d3 . hierarchy ( root ) ) ;
143
142
144
- // Calculate total required height and width
145
143
let minX = Infinity ;
146
144
let maxX = - Infinity ;
147
145
let minY = Infinity ;
@@ -157,16 +155,14 @@ function History(props: Record<string, unknown>): JSX.Element {
157
155
const actualWidth = maxX - minX + FIXED_NODE_WIDTH ;
158
156
const actualHeight = maxY - minY + FIXED_NODE_HEIGHT ;
159
157
160
- // Set SVG size to accommodate the entire tree
161
158
svg
162
159
. attr ( 'width' , Math . max ( actualWidth + margin . left + margin . right , totalWidth ) )
163
160
. attr ( 'height' , Math . max ( actualHeight + margin . top + margin . bottom , totalHeight ) ) ;
164
161
165
- // Center the root node horizontally
166
162
const centerOffset = totalWidth / 2 - ( maxX - minX ) / 2 ;
167
-
168
163
const g = svg . append ( 'g' ) . attr ( 'transform' , `translate(${ centerOffset } ,${ margin . top } )` ) ;
169
164
165
+ // Draw links
170
166
const link = g
171
167
. selectAll ( '.link' )
172
168
. data ( d3root . descendants ( ) . slice ( 1 ) )
@@ -183,6 +179,7 @@ function History(props: Record<string, unknown>): JSX.Element {
183
179
} ${ d . parent . x } ,${ d . parent . y } `,
184
180
) ;
185
181
182
+ // Create node groups
186
183
const node = g
187
184
. selectAll ( '.node' )
188
185
. data ( d3root . descendants ( ) )
@@ -194,92 +191,42 @@ function History(props: Record<string, unknown>): JSX.Element {
194
191
const activeClass = d . data . index === currLocation . index ? ' active' : '' ;
195
192
return baseClass + internalClass + activeClass ;
196
193
} )
197
- . attr ( 'transform' , ( d ) => `translate(${ d . x } ,${ d . y } )` ) ;
194
+ . attr ( 'transform' , ( d ) => `translate(${ d . x } ,${ d . y } )` )
195
+ . on ( 'click' , ( event , d ) => {
196
+ dispatch ( changeView ( d . data . index ) ) ;
197
+ dispatch ( changeSlider ( d . data . index ) ) ;
198
+ } ) ;
198
199
200
+ // Add rectangles for nodes
199
201
node
200
202
. append ( 'rect' )
201
- . attr ( 'width' , 100 ) // Width of rectangle
202
- . attr ( 'height' , 40 ) // Height of rectangle
203
- . attr ( 'x' , - 50 ) // Center the rectangle horizontally
204
- . attr ( 'y' , - 20 ) // Center the rectangle vertically
205
- . attr ( 'rx' , 8 ) // Rounded corners
206
- . attr ( 'ry' , 8 ) ; // Rounded corners
207
-
208
- // Add text with "Snapshot" prefix
203
+ . attr ( 'width' , 180 )
204
+ . attr ( 'height' , 80 )
205
+ . attr ( 'x' , - 90 )
206
+ . attr ( 'y' , - 40 )
207
+ . attr ( 'rx' , 8 )
208
+ . attr ( 'ry' , 8 ) ;
209
+
210
+ // Add snapshot title
209
211
node
210
212
. append ( 'text' )
211
- . attr ( 'dy' , '0.31em ' )
213
+ . attr ( 'dy' , '-25 ' )
212
214
. attr ( 'text-anchor' , 'middle' )
215
+ . attr ( 'class' , 'snapshot-title' )
213
216
. text ( ( d ) => `Snapshot ${ d . data . index + 1 } ` ) ;
214
217
215
- // Add click handler for nodes
218
+ // Add state changes text
216
219
node
217
- . on ( 'click' , ( event , d ) => {
218
- dispatch ( changeView ( d . data . index ) ) ;
219
- dispatch ( changeSlider ( d . data . index ) ) ;
220
-
221
- function renderToolTip ( ) {
222
- const [ x , y ] = d3 . pointer ( event ) ;
223
- const div = d3
224
- . select ( '.display:first-child' )
225
- . append ( 'div' )
226
- . attr ( 'class' , `tooltip` )
227
- . attr ( 'id' , `tt-${ d . data . index } ` )
228
- . style ( 'left' , `${ event . clientX - 10 } px` )
229
- . style ( 'top' , `${ event . clientY - 10 } px` )
230
- . style ( 'max-height' , `25%` )
231
- . style ( 'overflow' , `scroll` ) ;
232
- d3 . selectAll ( '.tooltip' ) . html ( findDiff ( d . data . index ) ) ;
233
- }
234
-
235
- if ( d3 . selectAll ( '.tooltip' ) . _groups [ '0' ] . length === 0 ) {
236
- renderToolTip ( ) ; //if there are no tooltips left in the doc, we call the function to create a new tooltip
237
- } else {
238
- if ( d3 . selectAll ( `#tt-${ d . data . index } ` ) . _groups [ '0' ] . length === 0 ) {
239
- // if there is no tooltip with the specific id
240
- d3 . selectAll ( '.tooltip' ) . remove ( ) ; //remove any existing tooltips
241
- renderToolTip ( ) ; //call the function again to create a new tooltip
242
- }
243
- }
244
- } )
245
- . on ( 'mouseenter' , function ( event , d ) {
246
- if ( d . data . index === 0 ) return ;
247
- d3 . selectAll ( '.tooltip' ) . remove ( ) ;
248
- const [ x , y ] = d3 . pointer ( event ) ;
249
- if ( d3 . selectAll ( '.tooltip' ) . _groups [ '0' ] . length === 0 ) {
250
- const div = d3
251
- . select ( '.display:first-child' )
252
- . append ( 'div' )
253
- . attr ( 'class' , `tooltip` )
254
- . attr ( 'id' , `tt-${ d . data . index } ` )
255
- . style ( 'left' , `${ event . clientX + 30 } px` )
256
- . style ( 'top' , `${ event . clientY - 75 } px` )
257
- . style ( 'max-height' , `25%` )
258
- . style ( 'overflow' , `auto` )
259
- . on ( 'mouseenter' , function ( event , d ) {
260
- d3 . select ( this ) . interrupt ( ) ; // Interrupt any ongoing transitions
261
- } )
262
- . on ( 'mouseleave' , function ( event , d ) {
263
- d3 . selectAll ( '.tooltip' ) . remove ( ) . style ( 'display' , 'hidden' ) ;
264
- } ) ;
265
-
266
- d3 . selectAll ( '.tooltip' ) . html ( findDiff ( d . data . index ) ) ;
267
- }
268
- } )
269
- . on ( 'mouseleave' , function ( event , d ) {
270
- if ( event . relatedTarget . id !== `tt-${ d . data . index } ` ) {
271
- d3 . selectAll ( '.tooltip' ) . transition ( ) . delay ( 0 ) . remove ( ) ;
272
- }
273
- } ) ;
274
-
275
- const tooltip = d3
276
- . select ( '.tooltip' )
277
- . on ( 'mousemove' , function ( event , d ) {
278
- d3 . select ( '.tooltip' ) . style ( 'opacity' , '1' ) ;
279
- } )
280
- . on ( 'mouseleave' , function ( event , d ) {
281
- d3 . selectAll ( '.tooltip' ) . remove ( ) ;
282
- } ) ;
220
+ . append ( 'foreignObject' )
221
+ . attr ( 'x' , - 85 )
222
+ . attr ( 'y' , - 15 )
223
+ . attr ( 'width' , 170 )
224
+ . attr ( 'height' , 65 )
225
+ . append ( 'xhtml:div' )
226
+ . style ( 'font-size' , '12px' )
227
+ . style ( 'overflow' , 'hidden' )
228
+ . style ( 'text-align' , 'center' )
229
+ . html ( ( d ) => findDiff ( d . data . index ) ) ;
283
230
284
231
return svg . node ( ) ;
285
232
} ;
0 commit comments