@@ -18,6 +18,9 @@ interface ModelNode extends d3.SimulationNodeDatum {
18
18
y? : number
19
19
fx? : number | null
20
20
fy? : number | null
21
+ // Track position for dragging
22
+ posX? : number
23
+ posY? : number
21
24
}
22
25
23
26
// Relationship link interface
@@ -469,24 +472,30 @@ const createDiagram = () => {
469
472
470
473
// Set initial positions for models (fixed layout similar to the reference image)
471
474
const initialPositions: Record <string , {x: number , y: number }> = {
472
- ' user' : { x: width / 2 , y: height / 3 },
473
- ' team' : { x: width / 2 - 250 , y: height / 2 + 150 },
474
- ' post' : { x: width / 2 + 250 , y: height / 3 },
475
- ' accessToken' : { x: width / 2 + 250 , y: height / 2 + 50 },
476
- ' subscriber' : { x: width / 2 + 100 , y: height / 2 + 200 },
477
- ' subscriberEmail' : { x: width / 2 + 250 , y: height / 2 + 350 },
478
- ' project' : { x: width / 2 - 100 , y: height / 2 + 50 },
479
- ' deployment' : { x: width / 2 - 250 , y: height / 2 + 300 },
475
+ ' user' : { x: width / 2 , y: height / 3 - 50 },
476
+ ' team' : { x: width / 2 - 300 , y: height / 2 + 100 },
477
+ ' post' : { x: width / 2 + 300 , y: height / 3 - 50 },
478
+ ' accessToken' : { x: width / 2 + 300 , y: height / 2 + 50 },
479
+ ' subscriber' : { x: width / 2 + 150 , y: height / 2 + 200 },
480
+ ' subscriberEmail' : { x: width / 2 + 300 , y: height / 2 + 350 },
481
+ ' project' : { x: width / 2 - 150 , y: height / 2 + 50 },
482
+ ' deployment' : { x: width / 2 - 300 , y: height / 2 + 300 },
480
483
' release' : { x: width / 2 , y: height / 2 + 200 },
481
- ' order' : { x: width / 2 - 350 , y: height / 3 },
482
- ' orderItem' : { x: width / 2 - 350 , y: height / 3 + 150 }
484
+ ' order' : { x: width / 2 - 300 , y: height / 3 - 50 },
485
+ ' orderItem' : { x: width / 2 - 300 , y: height / 3 + 150 }
483
486
}
484
487
485
- // Apply initial positions to models
488
+ // Apply initial positions to models and store them for dragging
486
489
models .forEach (model => {
487
490
if (initialPositions [model .id ]) {
488
- model .fx = initialPositions [model .id ].x
489
- model .fy = initialPositions [model .id ].y
491
+ const pos = initialPositions [model .id ]
492
+ if (pos ) {
493
+ model .fx = pos .x
494
+ model .fy = pos .y
495
+ // Store position for dragging
496
+ model .posX = pos .x
497
+ model .posY = pos .y
498
+ }
490
499
}
491
500
})
492
501
@@ -498,14 +507,18 @@ const createDiagram = () => {
498
507
const nodeGroup = g .append (' g' )
499
508
.attr (' class' , ' nodes' )
500
509
510
+ // Card width increased to accommodate longer property names
511
+ const cardWidth = 240
512
+
501
513
// Create nodes
502
514
const node = nodeGroup
503
515
.selectAll (' g' )
504
516
.data (models )
505
517
.join (' g' )
506
518
.attr (' transform' , d => {
507
- const pos = initialPositions [d .id ] || { x: width / 2 , y: height / 2 }
508
- return ` translate(${pos .x - 100 }, ${pos .y - 40 }) `
519
+ const x = d .posX || width / 2
520
+ const y = d .posY || height / 2
521
+ return ` translate(${x - cardWidth / 2 }, ${y - 40 }) `
509
522
})
510
523
.call (d3 .drag <any , ModelNode >()
511
524
.on (' start' , dragstarted )
@@ -514,7 +527,7 @@ const createDiagram = () => {
514
527
515
528
// Add shadow effect to nodes
516
529
node .append (' rect' )
517
- .attr (' width' , 200 )
530
+ .attr (' width' , cardWidth )
518
531
.attr (' height' , d => {
519
532
const propsHeight = d .properties .length * 24
520
533
const relationshipsHeight = d .relationships .length > 0 ?
@@ -529,7 +542,7 @@ const createDiagram = () => {
529
542
530
543
// Add main rectangles for nodes with color border
531
544
node .append (' rect' )
532
- .attr (' width' , 200 )
545
+ .attr (' width' , cardWidth )
533
546
.attr (' height' , d => {
534
547
// Calculate height based on properties and relationships
535
548
const propsHeight = d .properties .length * 24
@@ -551,15 +564,15 @@ const createDiagram = () => {
551
564
552
565
// Header background
553
566
header .append (' rect' )
554
- .attr (' width' , 200 )
567
+ .attr (' width' , cardWidth )
555
568
.attr (' height' , 40 )
556
569
.attr (' rx' , 8 )
557
570
.attr (' ry' , 8 )
558
571
.attr (' fill' , ' #1E293B' )
559
572
560
573
// Model name
561
574
header .append (' text' )
562
- .attr (' x' , 100 )
575
+ .attr (' x' , cardWidth / 2 )
563
576
.attr (' y' , 25 )
564
577
.attr (' text-anchor' , ' middle' )
565
578
.attr (' fill' , ' #E5E7EB' )
@@ -581,36 +594,39 @@ const createDiagram = () => {
581
594
const row = propertiesGroup .append (' g' )
582
595
.attr (' transform' , ` translate(0, ${y }) ` )
583
596
584
- // Primary key indicator (yellow dot for id)
597
+ // Primary key indicator (yellow dot for id) - fixed vertical alignment
585
598
if (prop .name === ' id' ) {
586
599
row .append (' circle' )
587
- .attr (' cx' , 10 )
600
+ .attr (' cx' , 12 ) // Adjusted for better alignment
588
601
.attr (' cy' , 0 )
589
602
.attr (' r' , 4 )
590
603
.attr (' fill' , ' #FCD34D' ) // Yellow for primary key
591
604
}
592
605
593
606
// Property name
594
607
row .append (' text' )
595
- .attr (' x' , 20 )
608
+ .attr (' x' , 24 ) // Adjusted for better spacing
596
609
.attr (' y' , 0 )
610
+ .attr (' dominant-baseline' , ' middle' ) // Improved vertical alignment
597
611
.attr (' fill' , prop .name === ' id' ? ' #FCD34D' : ' #E5E7EB' )
598
612
.attr (' font-size' , ' 14px' )
599
613
.text (prop .name )
600
614
601
615
// Property type
602
616
row .append (' text' )
603
- .attr (' x' , 180 )
617
+ .attr (' x' , cardWidth - 40 ) // Adjusted for wider card
604
618
.attr (' y' , 0 )
619
+ .attr (' dominant-baseline' , ' middle' ) // Improved vertical alignment
605
620
.attr (' text-anchor' , ' end' )
606
621
.attr (' fill' , ' #9CA3AF' )
607
622
.attr (' font-size' , ' 14px' )
608
623
.text (prop .type )
609
624
610
- // Nullable indicator
625
+ // Nullable indicator - improved spacing from edge
611
626
row .append (' text' )
612
- .attr (' x' , 195 )
627
+ .attr (' x' , cardWidth - 16 ) // Adjusted for better spacing from edge
613
628
.attr (' y' , 0 )
629
+ .attr (' dominant-baseline' , ' middle' ) // Improved vertical alignment
614
630
.attr (' text-anchor' , ' middle' )
615
631
.attr (' fill' , prop .nullable ? ' #EF4444' : ' #10B981' )
616
632
.attr (' font-size' , ' 14px' )
@@ -625,7 +641,7 @@ const createDiagram = () => {
625
641
g .append (' line' )
626
642
.attr (' x1' , 0 )
627
643
.attr (' y1' , relationshipsY )
628
- .attr (' x2' , 200 )
644
+ .attr (' x2' , cardWidth )
629
645
.attr (' y2' , relationshipsY )
630
646
.attr (' stroke' , ' #4B5563' )
631
647
.attr (' stroke-width' , 1 )
@@ -653,11 +669,11 @@ const createDiagram = () => {
653
669
bgColor = relationshipColors .belongsToMany
654
670
}
655
671
656
- // Add relationship background
672
+ // Add relationship background - adjusted to match reference image
657
673
row .append (' rect' )
658
- .attr (' x' , 10 )
674
+ .attr (' x' , 12 )
659
675
.attr (' y' , - 12 )
660
- .attr (' width' , 180 )
676
+ .attr (' width' , cardWidth - 24 )
661
677
.attr (' height' , 24 )
662
678
.attr (' rx' , 4 )
663
679
.attr (' ry' , 4 )
@@ -666,19 +682,21 @@ const createDiagram = () => {
666
682
.attr (' stroke' , bgColor )
667
683
.attr (' stroke-width' , 1 )
668
684
669
- // Relationship type
685
+ // Relationship type - improved vertical alignment
670
686
row .append (' text' )
671
- .attr (' x' , 20 )
687
+ .attr (' x' , 24 )
672
688
.attr (' y' , 0 )
689
+ .attr (' dominant-baseline' , ' middle' ) // Improved vertical alignment
673
690
.attr (' fill' , bgColor )
674
691
.attr (' font-size' , ' 14px' )
675
692
.attr (' font-weight' , ' bold' )
676
693
.text (relationshipType + ' :' )
677
694
678
- // Related model
695
+ // Related model - improved vertical alignment
679
696
row .append (' text' )
680
- .attr (' x' , 110 )
697
+ .attr (' x' , 120 )
681
698
.attr (' y' , 0 )
699
+ .attr (' dominant-baseline' , ' middle' ) // Improved vertical alignment
682
700
.attr (' fill' , ' #E5E7EB' )
683
701
.attr (' font-size' , ' 14px' )
684
702
.text (rel .model )
@@ -691,18 +709,18 @@ const createDiagram = () => {
691
709
const sourceId = typeof rel .source === ' string' ? rel .source : rel .source .id
692
710
const targetId = typeof rel .target === ' string' ? rel .target : rel .target .id
693
711
694
- const sourcePos = initialPositions [ sourceId ]
695
- const targetPos = initialPositions [ targetId ]
712
+ const sourceModel = models . find ( m => m . id === sourceId )
713
+ const targetModel = models . find ( m => m . id === targetId )
696
714
697
- if (sourcePos && targetPos ) {
715
+ if (sourceModel && targetModel && sourceModel . posX && sourceModel . posY && targetModel . posX && targetModel . posY ) {
698
716
// Calculate control points for the curve
699
- const dx = targetPos . x - sourcePos . x
700
- const dy = targetPos . y - sourcePos . y
717
+ const dx = targetModel . posX - sourceModel . posX
718
+ const dy = targetModel . posY - sourceModel . posY
701
719
const dr = Math .sqrt (dx * dx + dy * dy ) * 1.2 // Curve factor
702
720
703
721
// Create curved path
704
722
linkGroup .append (' path' )
705
- .attr (' d' , ` M${sourcePos . x },${sourcePos . y }A${dr },${dr } 0 0,1 ${targetPos . x },${targetPos . y } ` )
723
+ .attr (' d' , ` M${sourceModel . posX },${sourceModel . posY }A${dr },${dr } 0 0,1 ${targetModel . posX },${targetModel . posY } ` )
706
724
.attr (' fill' , ' none' )
707
725
.attr (' stroke' , () => {
708
726
// Color links based on relationship type
@@ -718,11 +736,23 @@ const createDiagram = () => {
718
736
}
719
737
})
720
738
721
- // Add a legend for relationship types
739
+ // Add a legend for relationship types with improved visibility
722
740
const legend = svg .append (' g' )
723
- .attr (' transform' , ` translate(${width - 200 }, 20) ` )
741
+ .attr (' transform' , ` translate(${width - 220 }, 20) ` )
724
742
.attr (' class' , ' legend' )
725
743
744
+ // Add legend background for better visibility
745
+ legend .append (' rect' )
746
+ .attr (' x' , - 10 )
747
+ .attr (' y' , - 10 )
748
+ .attr (' width' , 220 )
749
+ .attr (' height' , 130 )
750
+ .attr (' rx' , 8 )
751
+ .attr (' ry' , 8 )
752
+ .attr (' fill' , ' rgba(30, 41, 59, 0.8)' ) // Dark background with transparency
753
+ .attr (' stroke' , ' #4B5563' )
754
+ .attr (' stroke-width' , 1 )
755
+
726
756
const relationshipTypes = [
727
757
{ type: ' belongsTo' , label: ' Belongs To' , color: relationshipColors .belongsTo },
728
758
{ type: ' hasMany' , label: ' Has Many' , color: relationshipColors .hasMany },
@@ -732,7 +762,7 @@ const createDiagram = () => {
732
762
733
763
relationshipTypes .forEach ((rel , i ) => {
734
764
const legendItem = legend .append (' g' )
735
- .attr (' transform' , ` translate(0 , ${i * 25 }) ` )
765
+ .attr (' transform' , ` translate(10 , ${i * 25 + 15 }) ` )
736
766
737
767
// Line sample
738
768
legendItem .append (' line' )
@@ -745,12 +775,13 @@ const createDiagram = () => {
745
775
.attr (' stroke-dasharray' , rel .type === ' belongsToMany' ? ' 5,5' : ' none' )
746
776
.attr (' marker-end' , ` url(#arrow-${rel .type }) ` )
747
777
748
- // Label
778
+ // Label with improved visibility
749
779
legendItem .append (' text' )
750
780
.attr (' x' , 40 )
751
- .attr (' y' , 14 )
752
- .attr (' fill' , ' #E5E7EB' )
753
- .attr (' font-size' , ' 12px' )
781
+ .attr (' y' , 10 )
782
+ .attr (' dominant-baseline' , ' middle' ) // Improved vertical alignment
783
+ .attr (' fill' , ' #FFFFFF' ) // White text for better contrast
784
+ .attr (' font-size' , ' 14px' )
754
785
.text (rel .label )
755
786
})
756
787
@@ -762,19 +793,68 @@ const createDiagram = () => {
762
793
// Drag functions
763
794
function dragstarted(event : d3 .D3DragEvent <SVGGElement , ModelNode , ModelNode >) {
764
795
if (! event .active && simulation ) simulation .alphaTarget (0.3 ).restart ()
765
- event .subject .fx = event .subject .x
766
- event .subject .fy = event .subject .y
767
796
}
768
797
769
798
function dragged(event : d3 .D3DragEvent <SVGGElement , ModelNode , ModelNode >) {
770
- event .subject .fx = event .x
771
- event .subject .fy = event .y
799
+ // Update the model's position
800
+ event .subject .posX = event .x + 120 // Adjust for the transform offset
801
+ event .subject .posY = event .y + 40 // Adjust for the transform offset
802
+
803
+ // Update the node position
804
+ d3 .select (event .sourceEvent .currentTarget )
805
+ .attr (' transform' , ` translate(${event .x }, ${event .y }) ` )
806
+
807
+ // Redraw all links
808
+ updateLinks ()
772
809
}
773
810
774
811
function dragended(event : d3 .D3DragEvent <SVGGElement , ModelNode , ModelNode >) {
775
812
if (! event .active && simulation ) simulation .alphaTarget (0 )
776
813
}
777
814
815
+ // Function to update links after dragging
816
+ function updateLinks() {
817
+ if (! diagramContainer .value ) return
818
+
819
+ const svg = d3 .select (diagramContainer .value ).select (' svg' )
820
+ const linkGroup = svg .select (' .links' )
821
+
822
+ // Clear existing links
823
+ linkGroup .selectAll (' path' ).remove ()
824
+
825
+ // Redraw all links
826
+ relationships .forEach (rel => {
827
+ const sourceId = typeof rel .source === ' string' ? rel .source : rel .source .id
828
+ const targetId = typeof rel .target === ' string' ? rel .target : rel .target .id
829
+
830
+ const sourceModel = models .find (m => m .id === sourceId )
831
+ const targetModel = models .find (m => m .id === targetId )
832
+
833
+ if (sourceModel && targetModel && sourceModel .posX && sourceModel .posY && targetModel .posX && targetModel .posY ) {
834
+ // Calculate control points for the curve
835
+ const dx = targetModel .posX - sourceModel .posX
836
+ const dy = targetModel .posY - sourceModel .posY
837
+ const dr = Math .sqrt (dx * dx + dy * dy ) * 1.2 // Curve factor
838
+
839
+ // Create curved path
840
+ linkGroup .append (' path' )
841
+ .attr (' d' , ` M${sourceModel .posX },${sourceModel .posY }A${dr },${dr } 0 0,1 ${targetModel .posX },${targetModel .posY } ` )
842
+ .attr (' fill' , ' none' )
843
+ .attr (' stroke' , () => {
844
+ // Color links based on relationship type
845
+ if (rel .type === ' hasMany' ) return relationshipColors .hasMany
846
+ if (rel .type === ' hasOne' ) return relationshipColors .hasOne
847
+ if (rel .type === ' belongsToMany' ) return relationshipColors .belongsToMany
848
+ return relationshipColors .belongsTo // belongsTo
849
+ })
850
+ .attr (' stroke-width' , 2 )
851
+ .attr (' stroke-dasharray' , rel .type === ' belongsToMany' ? ' 5,5' : ' none' )
852
+ .attr (' marker-end' , ` url(#arrow-${rel .type }) ` )
853
+ .attr (' opacity' , 0.8 )
854
+ }
855
+ })
856
+ }
857
+
778
858
// Initialize visualization on mount
779
859
onMounted (() => {
780
860
createDiagram ()
0 commit comments