@@ -242,19 +242,38 @@ const updateVisualization = () => {
242
242
243
243
// Create force simulation with proper typing
244
244
simulation = d3 .forceSimulation <InfraNode >(nodes )
245
- .force (' link' , d3 .forceLink <InfraNode , InfraLink >(links ).id (d => d .id ).distance (100 ))
246
- .force (' charge' , d3 .forceManyBody <InfraNode >().strength (- 300 ))
247
- .force (' center' , d3 .forceCenter <InfraNode >(width / 2 , height / 2 ))
248
- .force (' collision' , d3 .forceCollide <InfraNode >().radius (50 ))
245
+ .force (' link' , d3 .forceLink <InfraNode , InfraLink >(links )
246
+ .id (d => d .id )
247
+ .distance (150 )
248
+ .strength (1 ))
249
+ .force (' charge' , d3 .forceManyBody ().strength (- 400 ))
250
+ .force (' x' , d3 .forceX <InfraNode >(d => {
251
+ switch (d .type ) {
252
+ case ' user' : return width * 0.2
253
+ case ' loadbalancer' : return width * 0.4
254
+ case ' server' : return width * 0.6
255
+ case ' worker' : return width * 0.8
256
+ default : return width * 0.5
257
+ }
258
+ }).strength (0.8 ))
259
+ .force (' y' , d3 .forceY <InfraNode >(d => {
260
+ if (d .type === ' server' || d .type === ' worker' ) {
261
+ const index = nodes .filter (n => n .type === ' server' || n .type === ' worker' ).indexOf (d )
262
+ return (height / 2 ) + (index * 40 ) - (nodes .length * 10 )
263
+ }
264
+ return height / 2
265
+ }).strength (0.8 ))
266
+ .force (' collision' , d3 .forceCollide <InfraNode >().radius (40 ))
249
267
250
268
// Create links
251
269
const link = zoomGroup .append (' g' )
252
270
.selectAll <SVGLineElement , InfraLink >(' line' )
253
271
.data (links )
254
272
.join (' line' )
255
- .attr (' stroke' , ' #999 ' )
256
- .attr (' stroke-opacity' , 0.6 )
273
+ .attr (' stroke' , ' #D1D5DB ' ) // gray-300 for better visibility
274
+ .attr (' stroke-opacity' , 0.8 )
257
275
.attr (' stroke-width' , 2 )
276
+ .attr (' stroke-dasharray' , ' 4,4' )
258
277
259
278
// Create nodes
260
279
const node = zoomGroup .append (' g' )
@@ -275,40 +294,63 @@ const updateVisualization = () => {
275
294
.attr (' r' , 30 )
276
295
.attr (' fill' , (d : InfraNode ) => {
277
296
switch (d .type ) {
278
- case ' user' : return ' #EC4899 ' // pink
279
- case ' loadbalancer' : return ' #60A5FA ' // blue
280
- case ' server' : return ' #34D399 ' // green
281
- case ' worker' : return ' #FBBF24 ' // yellow
282
- default : return ' #9CA3AF ' // gray
297
+ case ' user' : return ' #FCE7F3 ' // pink-100
298
+ case ' loadbalancer' : return ' #DBEAFE ' // blue-100
299
+ case ' server' : return ' #D1FAE5 ' // green-100
300
+ case ' worker' : return ' #FEF3C7 ' // yellow-100
301
+ default : return ' #F3F4F6 ' // gray-100
283
302
}
284
303
})
285
- .attr (' stroke' , ' #fff' )
286
- .attr (' stroke-width' , 2 )
304
+ .attr (' stroke' , (d : InfraNode ) => {
305
+ switch (d .type ) {
306
+ case ' user' : return ' #EC4899' // pink-500
307
+ case ' loadbalancer' : return ' #3B82F6' // blue-500
308
+ case ' server' : return ' #10B981' // green-500
309
+ case ' worker' : return ' #F59E0B' // yellow-500
310
+ default : return ' #6B7280' // gray-500
311
+ }
312
+ })
313
+ .attr (' stroke-width' , 3 )
287
314
288
- // Add labels
289
- node .append (' text' )
290
- .text ((d : InfraNode ) => d .label )
291
- .attr (' text-anchor' , ' middle' )
292
- .attr (' dy' , 40 )
293
- .attr (' fill' , ' #fff' )
294
- .attr (' font-size' , ' 12px' )
315
+ // Add icons with colored backgrounds
316
+ node .append (' circle' )
317
+ .attr (' r' , 20 )
318
+ .attr (' fill' , (d : InfraNode ) => {
319
+ switch (d .type ) {
320
+ case ' user' : return ' #BE185D' // pink-700
321
+ case ' loadbalancer' : return ' #1D4ED8' // blue-700
322
+ case ' server' : return ' #047857' // green-700
323
+ case ' worker' : return ' #B45309' // yellow-700
324
+ default : return ' #374151' // gray-700
325
+ }
326
+ })
295
327
296
328
// Add icons
297
329
node .append (' text' )
298
330
.attr (' text-anchor' , ' middle' )
299
- .attr (' dy' , 5 )
331
+ .attr (' dy' , 7 )
300
332
.attr (' fill' , ' #fff' )
301
- .attr (' font-size' , ' 20px' )
333
+ .attr (' font-size' , ' 18px' )
334
+ .attr (' font-family' , ' sans-serif' )
302
335
.text ((d : InfraNode ) => {
303
336
switch (d .type ) {
304
- case ' user' : return ' 👤 '
337
+ case ' user' : return ' 👥 '
305
338
case ' loadbalancer' : return ' ⚖️'
306
339
case ' server' : return ' 🖥️'
307
340
case ' worker' : return ' ⚙️'
308
341
default : return ' 📦'
309
342
}
310
343
})
311
344
345
+ // Update labels with better contrast
346
+ node .append (' text' )
347
+ .text ((d : InfraNode ) => d .label )
348
+ .attr (' text-anchor' , ' middle' )
349
+ .attr (' dy' , 50 )
350
+ .attr (' fill' , ' #1F2937' ) // gray-800
351
+ .attr (' font-size' , ' 12px' )
352
+ .attr (' font-weight' , ' 500' )
353
+
312
354
// Update positions on simulation tick
313
355
simulation .on (' tick' , () => {
314
356
link
@@ -544,22 +586,6 @@ const formatConfigDetails = (config: ServerConfig | WorkerConfig) => {
544
586
</p >
545
587
</div >
546
588
547
- <!-- View Toggle -->
548
- <div class =" mb-8 flex items-center gap-4" >
549
- <button
550
- v-for =" view in ['visual', 'code']"
551
- :key =" view"
552
- @click =" activeView = view as 'visual' | 'code'"
553
- :class =" {
554
- 'px-4 py-2 text-sm font-medium rounded-md': true,
555
- 'bg-blue-600 text-white': activeView === view,
556
- 'bg-gray-100 text-gray-700 dark:bg-blue-gray-700 dark:text-gray-300': activeView !== view,
557
- }"
558
- >
559
- {{ view.charAt(0).toUpperCase() + view.slice(1) }} Builder
560
- </button >
561
- </div >
562
-
563
589
<!-- Infrastructure Diagram -->
564
590
<div class =" mb-8 bg-white dark:bg-blue-gray-700 rounded-lg shadow" >
565
591
<div class =" p-6" >
0 commit comments