44< head >
55 < meta charset ="UTF-8 ">
66 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
7- < title > Advanced Wire Antenna Designer </ title >
7+ < title > Experimental Antenna Calculator </ title >
88 < style >
99 body {
1010 font-family : Arial, sans-serif;
8383 </ style >
8484</ head >
8585< body >
86- < h1 > Advanced Wire Antenna Designer </ h1 >
87-
86+ < h1 > Experimental Antenna Calculator </ h1 >
87+ < p > This is an experemental antenna calculation tool written largely by Generative AI. Use at your own risk. </ p >
8888 < div class ="section ">
8989 < h2 > Input Parameters</ h2 >
9090 < div id ="targetBands ">
@@ -185,6 +185,7 @@ <h2>Results</h2>
185185 < script >
186186 let bandFreqCount = 0 ;
187187 let hybridElementCount = 0 ;
188+ const suggestedFrequencies = [ 0.135 , 0.472 , 1.8 , 3.5 , 5.3 , 7 , 10.1 , 14 , 18 , 21 , 28 , 50 , 144 , 222 , 420 , 902 , 1240 ] ;
188189
189190 function addBandFrequency ( ) {
190191 const bandFrequencies = document . getElementById ( 'bandFrequencies' ) ;
@@ -193,14 +194,11 @@ <h2>Results</h2>
193194 div . innerHTML = `
194195 <label>
195196 Band ${ bandFreqCount + 1 } (MHz):
196- <input type="number" id="${ freqId } " step="0.1 " min="0.1" value="${ 7.1 + bandFreqCount * 7 } ">
197+ <input type="number" id="${ freqId } " step="0.001 " min="0.1" value="${ bandFreqCount === 0 ? 14 : suggestedFrequencies [ bandFreqCount % suggestedFrequencies . length ] } ">
197198 <button class="remove-btn" onclick="removeBandFrequency(this)">Remove</button>
198199 </label>
199200 ` ;
200201 bandFrequencies . appendChild ( div ) ;
201- if ( bandFreqCount === 0 ) {
202- document . getElementById ( freqId ) . value = 14.2 ; // Default first band
203- }
204202 bandFreqCount ++ ;
205203 }
206204
@@ -503,7 +501,33 @@ <h2>Results</h2>
503501 const plotHeight = canvas . height - 2 * margin ;
504502 const maxSWR = Math . max ( ...swrValues , 5 ) ; // Cap at 5 for visibility
505503
504+ // Sort targetBands and swrValues together
505+ const sortedPairs = targetBands . map ( ( freq , index ) => [ freq , swrValues [ index ] ] )
506+ . sort ( ( a , b ) => a [ 0 ] - b [ 0 ] ) ;
507+ const sortedBands = sortedPairs . map ( pair => pair [ 0 ] ) ;
508+ const sortedSWR = sortedPairs . map ( pair => pair [ 1 ] ) ;
509+
510+ // Grid
511+ ctx . strokeStyle = '#ddd' ;
512+ ctx . lineWidth = 0.5 ;
513+ sortedBands . forEach ( ( freq , index ) => {
514+ const x = margin + ( index / ( sortedBands . length - 1 ) ) * plotWidth ;
515+ ctx . beginPath ( ) ;
516+ ctx . moveTo ( x , margin ) ;
517+ ctx . lineTo ( x , canvas . height - margin ) ;
518+ ctx . stroke ( ) ;
519+ } ) ;
520+ [ 1 , 2 , 3 , 4 , 5 ] . forEach ( swr => {
521+ const y = canvas . height - margin - ( swr / maxSWR ) * plotHeight ;
522+ ctx . beginPath ( ) ;
523+ ctx . moveTo ( margin , y ) ;
524+ ctx . lineTo ( canvas . width - margin , y ) ;
525+ ctx . stroke ( ) ;
526+ } ) ;
527+
506528 // Axes
529+ ctx . strokeStyle = '#000' ;
530+ ctx . lineWidth = 1 ;
507531 ctx . beginPath ( ) ;
508532 ctx . moveTo ( margin , margin ) ;
509533 ctx . lineTo ( margin , canvas . height - margin ) ;
@@ -521,13 +545,13 @@ <h2>Results</h2>
521545 ctx . restore ( ) ;
522546
523547 // Ticks and labels
524- targetBands . forEach ( ( freq , index ) => {
525- const x = margin + ( index / ( targetBands . length - 1 ) ) * plotWidth ;
548+ sortedBands . forEach ( ( freq , index ) => {
549+ const x = margin + ( index / ( sortedBands . length - 1 ) ) * plotWidth ;
526550 ctx . beginPath ( ) ;
527551 ctx . moveTo ( x , canvas . height - margin ) ;
528552 ctx . lineTo ( x , canvas . height - margin + 5 ) ;
529553 ctx . stroke ( ) ;
530- ctx . fillText ( freq . toFixed ( 1 ) , x , canvas . height - margin + 15 ) ;
554+ ctx . fillText ( freq . toFixed ( freq < 1 ? 3 : 1 ) , x , canvas . height - margin + 15 ) ;
531555 } ) ;
532556 [ 1 , 2 , 3 , 4 , 5 ] . forEach ( swr => {
533557 const y = canvas . height - margin - ( swr / maxSWR ) * plotHeight ;
@@ -543,13 +567,23 @@ <h2>Results</h2>
543567 ctx . beginPath ( ) ;
544568 ctx . strokeStyle = '#FF0000' ;
545569 ctx . lineWidth = 2 ;
546- targetBands . forEach ( ( freq , index ) => {
547- const x = margin + ( index / ( targetBands . length - 1 ) ) * plotWidth ;
548- const y = canvas . height - margin - ( swrValues [ index ] / maxSWR ) * plotHeight ;
570+ sortedBands . forEach ( ( freq , index ) => {
571+ const x = margin + ( index / ( sortedBands . length - 1 ) ) * plotWidth ;
572+ const y = canvas . height - margin - ( sortedSWR [ index ] / maxSWR ) * plotHeight ;
549573 if ( index === 0 ) ctx . moveTo ( x , y ) ;
550574 else ctx . lineTo ( x , y ) ;
551575 } ) ;
552576 ctx . stroke ( ) ;
577+
578+ // Data points
579+ ctx . fillStyle = '#0000FF' ;
580+ sortedBands . forEach ( ( freq , index ) => {
581+ const x = margin + ( index / ( sortedBands . length - 1 ) ) * plotWidth ;
582+ const y = canvas . height - margin - ( sortedSWR [ index ] / maxSWR ) * plotHeight ;
583+ ctx . beginPath ( ) ;
584+ ctx . arc ( x , y , 4 , 0 , 2 * Math . PI ) ;
585+ ctx . fill ( ) ;
586+ } ) ;
553587 }
554588
555589 function calculateAntenna ( ) {
@@ -608,35 +642,78 @@ <h2>Results</h2>
608642 return `${ length . toFixed ( 2 ) } m (${ feet . toFixed ( 2 ) } ft, ${ inches . toFixed ( 2 ) } in)` ;
609643 }
610644
611- function estimateSWR ( length , freq , type ) {
612- const idealLength = type === 'quarterVertical' ?
613- ( speedOfLight / ( freq * 1000000 ) ) * 0.25 * velocityFactor :
614- ( speedOfLight / ( freq * 1000000 ) ) * 0.5 * velocityFactor ;
615- const deviation = Math . abs ( length - idealLength ) / idealLength ;
616- return Math . max ( 1 , 1 + deviation * 10 ) ;
617- }
618-
619- function estimateMultiElementSWR ( lengths , freqs , types ) {
620- let totalDeviation = 0 ;
621- freqs . forEach ( ( freq , index ) => {
622- const length = lengths [ index ] ;
623- const type = types [ index ] ;
645+ function estimateSWR ( lengths , freq , type , balunRatio , traps , designFreqs ) {
646+ // For single-element antennas, use the single length and design frequency
647+ const length = Array . isArray ( lengths ) ? lengths [ 0 ] : lengths ;
648+ const designFreq = Array . isArray ( designFreqs ) ? designFreqs [ 0 ] : designFreqs ;
649+ const isMultiElement = Array . isArray ( lengths ) && lengths . length > 1 ;
650+
651+ // Base SWR from resonance
652+ let baseSWR = 1 ;
653+ if ( isMultiElement ) {
654+ // Multi-element: find closest resonant frequency
655+ const deviations = designFreqs . map ( ( df , i ) => {
656+ const idealLength = type [ i ] === 'quarterVertical' ?
657+ ( speedOfLight / ( df * 1000000 ) ) * 0.25 * velocityFactor :
658+ ( speedOfLight / ( df * 1000000 ) ) * 0.5 * velocityFactor ;
659+ return Math . abs ( lengths [ i ] - idealLength ) / idealLength ;
660+ } ) ;
661+ const minDeviation = Math . min ( ...deviations ) ;
662+ baseSWR = 1 + minDeviation * 10 ;
663+ } else {
624664 const idealLength = type === 'quarterVertical' ?
625- ( speedOfLight / ( freq * 1000000 ) ) * 0.25 * velocityFactor :
626- ( speedOfLight / ( freq * 1000000 ) ) * 0.5 * velocityFactor ;
627- totalDeviation += Math . abs ( length - idealLength ) / idealLength ;
628- } ) ;
629- return Math . max ( 1 , 1 + ( totalDeviation / freqs . length ) * 5 ) . toFixed ( 1 ) ;
665+ ( speedOfLight / ( designFreq * 1000000 ) ) * 0.25 * velocityFactor :
666+ ( speedOfLight / ( designFreq * 1000000 ) ) * 0.5 * velocityFactor ;
667+ const deviation = Math . abs ( length - idealLength ) / idealLength ;
668+ baseSWR = 1 + deviation * 10 ;
669+ }
670+
671+ // Balun factor
672+ let balunFactor = 1 ;
673+ if ( useBalun && balunRatio ) {
674+ const ratios = { '1:1' : 1 , '4:1' : 4 , '9:1' : 9 } ;
675+ const impedanceFactor = isMultiElement ? 1 :
676+ ( type === 'foldedDipole' ? 4 :
677+ ( type === 'offCenterDipole' ? 4 :
678+ ( type === 'endFed' || type === 'randomWire' ? 9 : 1 ) ) ) ;
679+ balunFactor = Math . abs ( ratios [ balunRatio ] - impedanceFactor ) / impedanceFactor + 1 ;
680+ }
681+
682+ // Trap factor
683+ let trapFactor = 1 ;
684+ if ( useTraps && traps . length > 0 ) {
685+ traps . forEach ( trapFreq => {
686+ const freqRatio = freq / trapFreq ;
687+ if ( freqRatio > 1 ) {
688+ // Sharp rise above trap frequency, peaking at 10x base SWR within 5%
689+ const proximity = Math . min ( 1 , Math . max ( 0 , ( freqRatio - 1 ) / 0.05 ) ) ;
690+ trapFactor += 9 * proximity ; // Adds up to 9 at 5% above trap
691+ }
692+ } ) ;
693+ }
694+
695+ // Resonance dips for multi-element
696+ let resonanceFactor = 1 ;
697+ if ( isMultiElement ) {
698+ designFreqs . forEach ( df => {
699+ const freqDiff = Math . abs ( freq - df ) / df ;
700+ if ( freqDiff < 0.05 ) {
701+ // Dip towards 1 near resonant frequency
702+ resonanceFactor = Math . min ( resonanceFactor , 1 + ( freqDiff / 0.05 ) * ( baseSWR - 1 ) ) ;
703+ }
704+ } ) ;
705+ }
706+
707+ return Math . max ( 1 , baseSWR * balunFactor * trapFactor * resonanceFactor ) ;
630708 }
631709
632710 function determineBalunRatio ( type ) {
633711 if ( ! useBalun ) return null ;
634712 switch ( type ) {
635713 case 'endFed' :
636714 case 'randomWire' :
637- return '9:1' ; // Common for random wire and end-fed
715+ return '9:1' ;
638716 case 'offCenterDipole' :
639- return '4:1' ; // OCFD typically ~200Ω
640717 case 'foldedDipole' :
641718 return '4:1' ;
642719 case 'dipole' :
@@ -676,13 +753,13 @@ <h2>Results</h2>
676753 calcResults . push ( `- Antenna Type: ${ antennaType } ` ) ;
677754 calcResults . push ( `- Wire Material: ${ wireMaterial } ` ) ;
678755 calcResults . push ( `- Wire Gauge: ${ wireGauge } AWG` ) ;
679- calcResults . push ( `- Target Bands: ${ targetBands . map ( f => f . toFixed ( 1 ) ) . join ( ', ' ) } MHz` ) ;
756+ calcResults . push ( `- Target Bands: ${ targetBands . map ( f => f . toFixed ( f < 1 ? 3 : 1 ) ) . join ( ', ' ) } MHz` ) ;
680757 if ( balunRatio ) {
681758 calcResults . push ( `- Balun/UnUn: ${ balunRatio } ` ) ;
682759 }
683760
684761 if ( antennaType === 'dipole' ) {
685- const freq = targetBands [ 0 ] ; // Use lowest band for single-element design
762+ const freq = targetBands [ 0 ] ;
686763 const length = ( speedOfLight / ( freq * 1000000 ) ) * 0.5 * velocityFactor ;
687764 lengths . push ( length ) ;
688765 frequencies . push ( freq ) ;
@@ -697,7 +774,7 @@ <h2>Results</h2>
697774 } ) ;
698775 }
699776 if ( balunRatio ) results . push ( `- Balun (${ balunRatio } ): At center feedpoint` ) ;
700- swrValues = targetBands . map ( f => estimateSWR ( length , f , 'dipole' ) ) ;
777+ swrValues = targetBands . map ( f => estimateSWR ( length , f , 'dipole' , balunRatio , trapFrequencies , freq ) ) ;
701778
702779 calcResults . push ( `<b>Element 1: Half-Wave Dipole at ${ freq } MHz</b>` ) ;
703780 calcResults . push ( `- Wavelength (λ) = c / f = ${ speedOfLight } / (${ freq } × 10⁶) = ${ ( speedOfLight / ( freq * 1000000 ) ) . toFixed ( 2 ) } m` ) ;
@@ -718,7 +795,7 @@ <h2>Results</h2>
718795 } ) ;
719796 }
720797 if ( balunRatio ) results . push ( `- Balun (${ balunRatio } ): At base feedpoint` ) ;
721- swrValues = targetBands . map ( f => estimateSWR ( length * 2 , f , 'quarterVertical' ) ) ;
798+ swrValues = targetBands . map ( f => estimateSWR ( length * 2 , f , 'quarterVertical' , balunRatio , trapFrequencies , freq ) ) ;
722799
723800 calcResults . push ( `<b>Element 1: Quarter-Wave Vertical at ${ freq } MHz</b>` ) ;
724801 calcResults . push ( `- Wavelength (λ) = c / f = ${ speedOfLight } / (${ freq } × 10⁶) = ${ ( speedOfLight / ( freq * 1000000 ) ) . toFixed ( 2 ) } m` ) ;
@@ -741,7 +818,7 @@ <h2>Results</h2>
741818 } ) ;
742819 }
743820 if ( balunRatio ) results . push ( `- Balun (${ balunRatio } ): At top center feedpoint` ) ;
744- swrValues = targetBands . map ( f => estimateSWR ( length , f , 'foldedDipole' ) ) ;
821+ swrValues = targetBands . map ( f => estimateSWR ( length , f , 'foldedDipole' , balunRatio , trapFrequencies , freq ) ) ;
745822
746823 calcResults . push ( `<b>Element 1: Folded Dipole at ${ freq } MHz</b>` ) ;
747824 calcResults . push ( `- Wavelength (λ) = c / f = ${ speedOfLight } / (${ freq } × 10⁶) = ${ ( speedOfLight / ( freq * 1000000 ) ) . toFixed ( 2 ) } m` ) ;
@@ -761,7 +838,7 @@ <h2>Results</h2>
761838 } ) ;
762839 }
763840 if ( balunRatio ) results . push ( `- UnUn (${ balunRatio } ): At end feedpoint` ) ;
764- swrValues = targetBands . map ( f => estimateSWR ( length , f , 'endFed' ) ) ;
841+ swrValues = targetBands . map ( f => estimateSWR ( length , f , 'endFed' , balunRatio , trapFrequencies , freq ) ) ;
765842
766843 calcResults . push ( `<b>Element 1: End-Fed Wire at ${ freq } MHz</b>` ) ;
767844 calcResults . push ( `- Wavelength (λ) = c / f = ${ speedOfLight } / (${ freq } × 10⁶) = ${ ( speedOfLight / ( freq * 1000000 ) ) . toFixed ( 2 ) } m` ) ;
@@ -784,11 +861,10 @@ <h2>Results</h2>
784861 } ) ;
785862 }
786863 if ( balunRatio ) results . push ( `- UnUn (${ balunRatio } ): At end feedpoint` ) ;
787- swrValues = targetBands . map ( f => estimateSWR ( length , f , 'endFed' ) ) ; // Approximate as end-fed
864+ swrValues = targetBands . map ( f => estimateSWR ( length , f , 'randomWire' , balunRatio , trapFrequencies , targetBands [ 0 ] ) ) ;
788865
789866 calcResults . push ( `<b>Element 1: Random Wire</b>` ) ;
790867 calcResults . push ( `- User-Specified Length: ${ length . toFixed ( 2 ) } m` ) ;
791- calcResults . push ( `- Note: SWR varies significantly; tuner recommended.` ) ;
792868 } else if ( antennaType === 'offCenterDipole' ) {
793869 const freq = targetBands [ 0 ] ;
794870 const length = ( speedOfLight / ( freq * 1000000 ) ) * 0.5 * velocityFactor ;
@@ -806,7 +882,7 @@ <h2>Results</h2>
806882 } ) ;
807883 }
808884 if ( balunRatio ) results . push ( `- Balun (${ balunRatio } ): At off-center feedpoint` ) ;
809- swrValues = targetBands . map ( f => estimateSWR ( length , f , 'dipole' ) * 1.2 ) ; // Higher SWR due to OCFD impedance
885+ swrValues = targetBands . map ( f => estimateSWR ( length , f , 'offCenterDipole' , balunRatio , trapFrequencies , freq ) ) ;
810886
811887 calcResults . push ( `<b>Element 1: Off-Center Fed Dipole at ${ freq } MHz</b>` ) ;
812888 calcResults . push ( `- Wavelength (λ) = c / f = ${ speedOfLight } / (${ freq } × 10⁶) = ${ ( speedOfLight / ( freq * 1000000 ) ) . toFixed ( 2 ) } m` ) ;
@@ -834,9 +910,7 @@ <h2>Results</h2>
834910 } ) ;
835911 }
836912 if ( balunRatio ) results . push ( `- Balun (${ balunRatio } ): At common center feedpoint` ) ;
837- const combinedSWR = estimateMultiElementSWR ( lengths , targetBands , types ) ;
838- results . push ( `- Expected SWR across all bands: ${ combinedSWR } ` ) ;
839- swrValues = targetBands . map ( ( ) => parseFloat ( combinedSWR ) ) ;
913+ swrValues = targetBands . map ( f => estimateSWR ( lengths , f , types , balunRatio , trapFrequencies , frequencies ) ) ;
840914 } else if ( antennaType === 'hybrid' ) {
841915 const hybridTypes = [ ] ;
842916 for ( let i = 0 ; i < hybridElementCount ; i ++ ) {
@@ -884,9 +958,7 @@ <h2>Results</h2>
884958 } ) ;
885959 }
886960 if ( balunRatio ) results . push ( `- Balun (${ balunRatio } ): At common feedpoint` ) ;
887- const combinedSWR = estimateMultiElementSWR ( lengths , targetBands , types ) ;
888- results . push ( `- Expected SWR across all bands: ${ combinedSWR } ` ) ;
889- swrValues = targetBands . map ( ( ) => parseFloat ( combinedSWR ) ) ;
961+ swrValues = targetBands . map ( f => estimateSWR ( lengths , f , types , balunRatio , trapFrequencies , frequencies ) ) ;
890962 }
891963
892964 if ( useTraps && trapFrequencies . length > 0 ) {
0 commit comments