@@ -613,32 +613,80 @@ class Visualizer {
613613 * network complement IS the desired output, no inverter needed
614614 * 7. Any NOT(VAR) leaves get dedicated input inverter sub-circuits
615615 */
616+
617+ /**
618+ * Measure a PDN/PUN tree to determine how much space it needs.
619+ * width = total parallel leaf devices (horizontal extent)
620+ * depth = longest series chain (vertical extent)
621+ */
622+ measureTree ( node ) {
623+ if ( ! node ) return { width : 1 , depth : 1 } ;
624+ if ( node . type === 'device' ) return { width : 1 , depth : 1 } ;
625+ if ( node . type === 'series' ) {
626+ let totalDepth = 0 ;
627+ let maxWidth = 0 ;
628+ for ( const child of node . children ) {
629+ const m = this . measureTree ( child ) ;
630+ totalDepth += m . depth ;
631+ maxWidth = Math . max ( maxWidth , m . width ) ;
632+ }
633+ return { width : maxWidth , depth : totalDepth } ;
634+ }
635+ if ( node . type === 'parallel' ) {
636+ let totalWidth = 0 ;
637+ let maxDepth = 0 ;
638+ for ( const child of node . children ) {
639+ const m = this . measureTree ( child ) ;
640+ totalWidth += m . width ;
641+ maxDepth = Math . max ( maxDepth , m . depth ) ;
642+ }
643+ return { width : totalWidth , depth : maxDepth } ;
644+ }
645+ return { width : 1 , depth : 1 } ;
646+ }
647+
616648 renderCMOSDiagram ( svgElement ) {
617649 svgElement . innerHTML = '' ;
618650 const ns = 'http://www.w3.org/2000/svg' ;
619- svgElement . setAttribute ( 'viewBox' , '0 0 1800 1200' ) ;
651+ // Prepare: strip outer complement, expand, track inverted inputs
652+ const { coreFn, needsOutputInverter, invertedInputs } = this . prepareForCMOS ( this . ast ) ;
653+ const pdnTree = this . buildPDNTree ( coreFn ) ;
654+ const punTree = this . dualizePDNtoPUN ( pdnTree ) ;
655+
656+ // Measure trees to compute dynamic layout
657+ const pdnSize = this . measureTree ( pdnTree ) ;
658+ const punSize = this . measureTree ( punTree ) ;
659+ const maxWidth = Math . max ( pdnSize . width , punSize . width , 1 ) ;
660+ const totalDepth = pdnSize . depth + punSize . depth ;
661+
662+ // Dynamic spacing — scales with complexity
663+ const transistorSpacingH = Math . max ( 200 , 160 + maxWidth * 20 ) ;
664+ const transistorSpacingV = Math . max ( 140 , 120 + totalDepth * 15 ) ;
665+
666+ // Dynamic canvas — grows with circuit, infinite room
667+ const numInverters = invertedInputs . size ;
668+ const leftMargin = numInverters > 0 ? 400 : 100 ;
669+ const neededWidth = leftMargin + maxWidth * transistorSpacingH + ( needsOutputInverter ? 900 : 500 ) ;
670+ const neededHeight = 300 + totalDepth * transistorSpacingV + 500 ;
671+ const canvasW = Math . max ( 1800 , neededWidth ) ;
672+ const canvasH = Math . max ( 1200 , neededHeight ) ;
673+
674+ svgElement . setAttribute ( 'viewBox' , `0 0 ${ canvasW } ${ canvasH } ` ) ;
620675
621676 const bg = document . createElementNS ( ns , 'rect' ) ;
622- bg . setAttribute ( 'width' , '1800' ) ;
623- bg . setAttribute ( 'height' , '1200' ) ;
677+ bg . setAttribute ( 'width' , String ( canvasW ) ) ;
678+ bg . setAttribute ( 'height' , String ( canvasH ) ) ;
624679 bg . setAttribute ( 'fill' , 'white' ) ;
625680 svgElement . appendChild ( bg ) ;
626681
627682 const g = document . createElementNS ( ns , 'g' ) ;
628683 g . setAttribute ( 'id' , 'cmosGroup' ) ;
629684
630- // Layout parameters
631- const mainCenterX = 600 ;
685+ // Dynamic positions based on circuit size
686+ const mainCenterX = leftMargin + ( maxWidth * transistorSpacingH ) / 2 + 100 ;
632687 const vddY = 80 ;
633- const gndY = 1120 ;
634- const outputY = 600 ;
635- const transistorSpacingH = 180 ;
636- const transistorSpacingV = 140 ;
637-
638- // Prepare: strip outer complement, expand, track inverted inputs
639- const { coreFn, needsOutputInverter, invertedInputs } = this . prepareForCMOS ( this . ast ) ;
640- const pdnTree = this . buildPDNTree ( coreFn ) ;
641- const punTree = this . dualizePDNtoPUN ( pdnTree ) ;
688+ const gndY = canvasH - 80 ;
689+ const outputY = ( vddY + gndY ) / 2 ;
642690
643691 // ============================================
644692 // MAIN NETWORK
@@ -695,8 +743,8 @@ class Visualizer {
695743 // ============================================
696744 if ( invertedInputs . size > 0 ) {
697745 const invVars = Array . from ( invertedInputs ) ;
698- const invSpacing = Math . min ( 350 , ( gndY - vddY - 200 ) / Math . max ( invVars . length , 1 ) ) ;
699- const invX = 200 ;
746+ const invSpacing = Math . max ( 300 , Math . min ( 400 , ( gndY - vddY - 200 ) / Math . max ( invVars . length , 1 ) ) ) ;
747+ const invX = Math . max ( 200 , mainCenterX - maxWidth * transistorSpacingH / 2 - 200 ) ;
700748 invVars . forEach ( ( varName , i ) => {
701749 const invCenterY = vddY + 180 + i * invSpacing ;
702750 this . drawInputInverterCircuit ( g , invX , invCenterY , varName ) ;
@@ -707,7 +755,7 @@ class Visualizer {
707755 // OUTPUT SECTION (conditional inverter)
708756 // ============================================
709757 if ( needsOutputInverter ) {
710- const inverterX = mainCenterX + 500 ;
758+ const inverterX = mainCenterX + Math . max ( 500 , maxWidth * transistorSpacingH / 2 + 300 ) ;
711759 const gateConnectionX = inverterX - 70 ;
712760
713761 // Horizontal connection to inverter gate
@@ -821,7 +869,7 @@ class Visualizer {
821869 g . appendChild ( finalLabel ) ;
822870 } else {
823871 // No output inverter — direct output from main network
824- const finalX = mainCenterX + 300 ;
872+ const finalX = mainCenterX + Math . max ( 300 , maxWidth * transistorSpacingH / 2 + 200 ) ;
825873
826874 const outLine = document . createElementNS ( ns , 'line' ) ;
827875 outLine . setAttribute ( 'x1' , mainCenterX ) ;
@@ -875,7 +923,7 @@ class Visualizer {
875923
876924 for ( let i = 0 ; i < node . children . length ; i ++ ) {
877925 const child = node . children [ i ] ;
878- const result = this . renderCMOSTree ( group , child , x , currentY , hSpacing * 0.7 , vSpacing ) ;
926+ const result = this . renderCMOSTree ( group , child , x , currentY , hSpacing , vSpacing ) ;
879927
880928 if ( i === 0 ) {
881929 firstResult = result ;
@@ -905,17 +953,21 @@ class Visualizer {
905953 } ;
906954 }
907955
908- // Parallel: place children side by side, connect tops and bottoms
956+ // Parallel: place children side by side — allocate width proportional to each child's needs
909957 if ( node . type === 'parallel' ) {
910- const numChildren = node . children . length ;
911- const totalWidth = ( numChildren - 1 ) * hSpacing ;
912- const startX = x - totalWidth / 2 ;
958+ const childMeasures = node . children . map ( c => this . measureTree ( c ) ) ;
959+ const totalLeaves = childMeasures . reduce ( ( s , m ) => s + m . width , 0 ) ;
960+ const totalWidth = Math . max ( totalLeaves , node . children . length ) * hSpacing ;
913961
914962 const results = [ ] ;
915- for ( let i = 0 ; i < numChildren ; i ++ ) {
916- const childX = startX + i * hSpacing ;
917- const result = this . renderCMOSTree ( group , node . children [ i ] , childX , y , hSpacing * 0.6 , vSpacing ) ;
963+ let currentX = x - totalWidth / 2 ;
964+ for ( let i = 0 ; i < node . children . length ; i ++ ) {
965+ const childW = childMeasures [ i ] . width ;
966+ const slotWidth = ( childW / totalLeaves ) * totalWidth ;
967+ const childX = currentX + slotWidth / 2 ;
968+ const result = this . renderCMOSTree ( group , node . children [ i ] , childX , y , hSpacing , vSpacing ) ;
918969 results . push ( result ) ;
970+ currentX += slotWidth ;
919971 }
920972
921973 // Find bounds
0 commit comments