kopia lustrzana https://github.com/miguelvaca/vk3cpu
1893 wiersze
96 KiB
HTML
1893 wiersze
96 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>VK3CPU Magnetic Loop Antenna Calculator</title>
|
|
<link rel="stylesheet" href="magloop.css">
|
|
</head>
|
|
<body>
|
|
<header>Miguel <a href="mailto:vk3cpu@gmail.com">VK3CPU</a> - Magloop Antenna Calculator V10.4</header>
|
|
<section class="gridLayoutClass">
|
|
<div class="chart-container" style="position: relative;">
|
|
<canvas id="chartCanvas" class="chartCanvasClass">
|
|
2D Chart Canvas
|
|
</canvas>
|
|
</div>
|
|
<div class="slider_container">
|
|
<div class="sliders">
|
|
<label for="conductor_diameter_slider">⌀a:</label>
|
|
<input type="range" id="conductor_diameter_slider" min="5" max="80" value="19" step="0.2">
|
|
</div>
|
|
<div class="sliders">
|
|
<label for="loop_diameter_slider">⌀b:</label>
|
|
<input type="range" id="loop_diameter_slider" min="0.04" max="5.0" value="1.0" step="0.01">
|
|
</div>
|
|
<div class="sliders">
|
|
<label for="loop_turns_slider">N:</label>
|
|
<input type="range" id="loop_turns_slider" min="0" max="8" value="1" step="1">
|
|
</div>
|
|
<div class="sliders">
|
|
<label for="loop_spacing_slider">c/a:</label>
|
|
<input type="range" id="loop_spacing_slider" min="1.1" max="30.0" value="2.0" step="0.01">
|
|
</div>
|
|
<div class="sliders">
|
|
<label for="transmit_power_slider">Tx:</label>
|
|
<input type="range" id="transmit_power_slider" min="5" max="1500" value="100" step="5">
|
|
</div>
|
|
<div class="sliders">
|
|
<label for="external_losses_slider">Re:</label>
|
|
<input type="range" id="external_losses_slider" min="0" max="1000" value="0" step="1">
|
|
</div>
|
|
<div class="radios">
|
|
<label>Met</label>
|
|
<input type="radio" name="unit_radio" id="metric_radio" value="metric" checked/>
|
|
<label>Imp</label>
|
|
<input type="radio" name="unit_radio" id="imperial_radio" value="imperial"/>
|
|
<label>Cu</label>
|
|
<input type="radio" name="metal_radio" id="copper_radio" value="Cu" checked/>
|
|
<label>Al</label>
|
|
<input type="radio" name="metal_radio" id="aluminium_radio" value="Al"/>
|
|
<label>Circ</label>
|
|
<input type="radio" name="shape_radio" id="circle_radio" value="circle" checked/>
|
|
<label>Oct</label>
|
|
<input type="radio" name="shape_radio" id="oct_radio" value="octagon"/>
|
|
<label>Hex</label>
|
|
<input type="radio" name="shape_radio" id="hex_radio" value="hexagon"/>
|
|
<label>Sqr</label>
|
|
<input type="radio" name="shape_radio" id="square_radio" value="square"/>
|
|
</div>
|
|
</div>
|
|
<div id="antenna-front-container" class="antennaFront-container" style="position: relative;">
|
|
<canvas id="antennaFront2D" class="antennaFrontClass" width="150" height="150">
|
|
</canvas>
|
|
</div>
|
|
<div id="antenna-side-container" class="antennaSide-container" style="position: relative;">
|
|
<canvas id="antennaSide2D" class="antennaSideClass" width="150" height="150">
|
|
</canvas>
|
|
</div>
|
|
<div class="notes">
|
|
<p style="text-align:center"><b><u><a href="./magloop_equations.html"> EQUATIONS USED </a></u></b></p>
|
|
<b><u>Notes:</u></b><br>
|
|
The Magloop Antenna Calculator was developed to predict the characteristics of a small-transmitting-loop (aka "STL", "magnetic loop" or "magloop")
|
|
antenna, given physical dimensions entered via slider widgets. <br>
|
|
It supports:
|
|
<ul>
|
|
<li>circular, octagonal, hexagonal and square-shaped loops</li>
|
|
<li>main loops made from either hollow round anodised-copper or aluminium conductors</li>
|
|
<li>metric and imperial units</li>
|
|
<li>magloops with 1-to-8 turns</li>
|
|
</ul>
|
|
I developed this multi-turn capable magloop calculator to take advantage of the
|
|
touch-screens and high-speed of modern mobile phones, to allow users to get realtime feedback of the predicted
|
|
behaviour of a magloop antenna. <br>-- 73 de VK3CPU<br><br>
|
|
<u><b>Inputs via the slider and radio widgets:</b></u>
|
|
<ul>
|
|
<li>⌀a : Conductor diameter in millimeters (mm) or inches ("). (Measured between opposing conductor outer surfaces.)</li>
|
|
<li>⌀b : Loop diameter in meters (m) or feet ('). (Measured between the conductor centers.)</li>
|
|
<li>N : Number of turns or loops. Sweeping left-to-right for parallel multiloop, then single loop, then series multiloop configurations. A "(P)" indicates a multiloop antenna configured with parallel main loops.</li>
|
|
<li>c/a : is the spacing ratio; based on 'c' being the inter-winding spacing for multi-turn loops measured between conductor centers, and 'a' is the conductor diameter. (Must be >= 1.1)
|
|
A low-value will increase the resistance due to the proximity effect. (Ignore for single-turn loops.)</li>
|
|
<li>Tx : The transmit power in Watts. This affects the predicted voltage across the capacitor (Vcap), and the RMS loop current (Ia).</li>
|
|
<li>Re : Additional resistance due to external losses, due mainly from capacitor contact resistance and proximity-to-ground effects.
|
|
Use Re=0.0 to assume the loop is in free-space with no capacitor losses (i.e. ideal conditions, with loop-related losses only).
|
|
Adding Re will reduce antenna efficiency, Q, Vcap and Ia, while increasing antenna BW.
|
|
According to [1] and [2], a 1 m diameter loop of 22 mm copper tubing at a height of 1.5 m above the ground operating at 7 MHz had a calculated capacitor contact resistance of ~190 mΩ
|
|
and an additional ground proximity loss resistance of ~30 mΩ. Note that true ground losses are dependent on both frequency and height-above-ground.</li>
|
|
<li>Metric or Imperial : selects the measuring system.</li>
|
|
<li>Cu or Al : selects the type of metal conductor (annealed copper or aluminum).</li>
|
|
<li>Circ, Oct, Hex or Sqr : selects the shape of the magloop. </li>
|
|
</ul>
|
|
<u><b>Calculated parameters:</b></u>
|
|
<ul>
|
|
<li>L : Inductance in microhenries. Equations from [3].</li>
|
|
<li>A : Loop area in square meters or square feet.</li>
|
|
<li>C : Effective capacitance of the loop in picofarads.</li>
|
|
<li>peri : Perimeter of the main loop in meters or feet.</li>
|
|
<li>c : Distance between windings, measured from the conductor centers in mm or inches.</li>
|
|
<li>cond : Total required conductor length in meters or feet.</li>
|
|
<li>Tuning Cap (pF): The capacitance required to bring the loop into resonance at the given frequency. Value in picofarads.</li>
|
|
<li>Vcap (kV): The predicted voltage across the capacitance given the desired transmit power.</li>
|
|
<li>BW (kHz): The predicted 3dB bandwidth of the magloop antenna. </li>
|
|
<li>Efficiency (%): The percentage of input energy that is actually radiated and not lost as heat. This efficiency figure is based on conductor losses of the main loop only.
|
|
Missing, are losses due to the capacitor, feedline and the near-field environment. These losses can be added via the Re slider.</li>
|
|
<li>R-radiation (Ω): The calculated radiation resistance of the loop in ohms.</li>
|
|
<li>R-loop (Ω): The calculated resistance of the loop in ohms, due to the combination of material conductance, conductor length, skin-effect and proximity effects.</li>
|
|
<li>Reactance (jΩ): The inductive reactance of the loop in ohms.</li>
|
|
<li>Q : The antenna Q (quality) factor.</li>
|
|
<li>Ia (A): The RMS loop current in amps.</li>
|
|
<li>Perimeter (λ): Antenna perimeter size relative to the wavelength.</li>
|
|
<li>Skin depth (μm): Conductor skin depth in micrometers.</li>
|
|
</ul>
|
|
<u><b>Usage hints:</b></u>
|
|
<li>Tap on legend items to disable or enable an output parameter. This can be used to declutter the chart.</li>
|
|
<li>Tap on a chart 'dot' to display a tooltip containing calculated output parameters for that frequency or band.</li>
|
|
<br>
|
|
<u><b>Other VK3CPU calculators:</b>
|
|
<ul>
|
|
<li><a href="https://miguelvaca.github.io/vk3cpu/toroid.html">RF Toroid Calculator</a></li>
|
|
<li><a href="https://miguelvaca.github.io/vk3cpu/inductor_imp.html">RF Inductor Calculator</a></li>
|
|
<li><a href="https://miguelvaca.github.io/vk3cpu/short_dipole.html">Coil-loaded Dipole Antenna Calculator</a></li>
|
|
</ul>
|
|
</u>
|
|
<br>
|
|
<b><u>References:</u></b><br>
|
|
[1]: B. Austin, A. Boswell and M. Perks, <b>"Loss Mechanisms in the Electrically Small Loop Antenna"</b> <i>, IEEE Antennas and Propagation Magazine, 56, 4, August 2014, pp. 143.</i> <br>
|
|
[2]: A. Boswell, A. J. Tyler and A. White, <b>"Performance of a Small Loop Antenna in the 3 - 10 MHz Band"</b> <i>, IEEE Antennas and Propagation Magazine, 47, 2, April 2005, pp. 5 1 -56.</i> <br>
|
|
[3]: F W Grover, <b>"Formulas and Tables for the Calculation of the Inductance of Coils of Polygonal Form"</b> <i>Scientific Papers of the Bureau of Standards, Vol 18, p753</i> <br>
|
|
<br>
|
|
<b><u>Change history:</u></b><br>
|
|
<b>[21-Dec-23] - V10.4</b> <br>
|
|
* Added CB frequencies (11m, 26.965 - 27.855 MHz). <br>
|
|
<b>[26-Aug-23] - V10.3</b> <br>
|
|
* Added support for URL parameters. Some code-base clean-up. <br>
|
|
<b>[13-May-23] - V10.2</b> <br>
|
|
* Changed radiation and loss resistance to logarithmic. Capacitance limited to 5-5000pF. <br>
|
|
<b>[8-May-23] - V10.1</b> <br>
|
|
* Stopped autoscaling on all axes. Changed Vcap, Icap to logarithmic. This makes it easier when comparing different configurations.<br>
|
|
<b>[8-May-23] - V10</b> <br>
|
|
* Added conductor skin depth as a new calculated parameter.<br>
|
|
<b>[23-Apr-23] - V9</b> <br>
|
|
* Changed the frequency list to include VHF (2m) and UHF (70cm) bands.<br>
|
|
* Reduced minimum loop diameter to 4 cm.<br>
|
|
<b>[12-Feb-22] - V8</b> <br>
|
|
* Changed the frequency list to include top and bottom frequencies of each band. This is to highlight the range of capacitance required between the top and bottom of each band.<br>
|
|
* Increased max c/a ratio from 10 to 30.<br>
|
|
<b>[13-Jan-22] - V8</b> <br>
|
|
* Added support for parallel conductor magloop antennas.<br>
|
|
<b>[21-Nov-21] - V7</b> <br>
|
|
* Upgrade Chart.js to the latest version, v3.5.1.<br>
|
|
* Tooltips are now justified, (using monospace fonts) and support changing metric prefix.<br>
|
|
<b>[27-Sep-21]</b> <br>
|
|
* Added band wavelength to tooltip display. Changed 60 m band from 5.0 to 5.3 MHz..<br>
|
|
<b>[26-Sep-21]</b> <br>
|
|
* Added metal type to schematic display.<br>
|
|
* Reduced display precision for Cap and Q in tooltip.<br>
|
|
* Changed Tuning Cap scale max from 2000 to 1000 pF.<br>
|
|
<b>[23-Sep-21]</b> <br>
|
|
* Changed Q equation back to the original Xl/Rtot. Changed max Q to 4000.<br>
|
|
* Introduced a new slider "Re" to inject external losses to account for the combined losses due to capacitor contact resistance and ground losses.<br>
|
|
* Renamed R-loss to R-loop to avoid confusion, as loop resistance is no longer the only resistance that contributes to losses. The other being Re.<br>
|
|
* Changed to V6 to capture the significant changes.<br>
|
|
<b>[22-Sep-21]</b> <br>
|
|
* Added antenna perimeter size in wavelength to the chart display as a new item.<br>
|
|
* Changed maximum spacing ratio c/a from 4.0 to 10.0. Values higher than 4 have no further effect on proximity resistance, but does reduce coil inductance which drives up the SRF.<br>
|
|
<b>[21-Sep-21]</b> <br>
|
|
* Added distributed capacitance calculation and display for the single turn loop.<br>
|
|
<b>[19-Sep-21]</b> <br>
|
|
* Increased supported conductor diameter to 80 mm. (3.15 inches)<br>
|
|
<b>[18-Sep-21]</b> <br>
|
|
* Updated to V5; Added support for octagon, hexagon and square shaped loops. Moved and hyperlinked equations-used to a separate page for clarity.<br>
|
|
<b>[16-Sep-21]</b> <br>
|
|
* Updated to V4; Updated equation used for Q to match the one use in the ARRL Antenna Book. This will affect predictions for V_cap, I_loop and BW. (Based on Q equation D.1 used in
|
|
<i>"Impedance, Bandwidth, and Q of Antennas"</i> by A D Yaghjian, IEEE Transactions on Antennas and Propagation, April 2005.)<br>
|
|
* Added equation graphics for V_cap, I_loop and BW formulas.<br>
|
|
* Flipped the main-loop graphic to have the capacitor above the coupling loop.<br>
|
|
<b>[12-Sep-21]</b> <br>
|
|
* Set maximum values to Q, Vcap and I axes to stop autoscaling. Max Q set to 2000, Vcap to 20 kV and I to 100 A.<br>
|
|
* Added formula/equation graphics in Notes section. A few more complex ones, such as effective capacitance and SRF, are still needed.<br>
|
|
* Fixed minor error in calculation of resistive loss due to proximity effect.<br>
|
|
<b>[11-Sep-21]</b> <br>
|
|
* Added visual cues for all slider-controlled parameters to highlight which parameter is being modified in the graphic representation.<br>
|
|
<b>[10-Sep-21]</b> <br>
|
|
* Added c/a display to graphic representation. Moved N from center to left.<br>
|
|
<b>[30-Aug-21]</b> <br>
|
|
* Added SRF calculation and display for multi-loop antennas.<br>
|
|
<b>[28-Aug-21]</b> <br>
|
|
* Added support for imperial units and for aluminum metal.<br>
|
|
<b>[27-Jul-21]</b> <br>
|
|
* Added total conductor length display.<br>
|
|
<b>[24-Jul-21]</b> <br>
|
|
* Added loop circumference display.<br>
|
|
<br>
|
|
</div>
|
|
</section>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
|
|
<script>
|
|
// GUI control widgets:
|
|
var loop_diameter_slider = document.getElementById("loop_diameter_slider");
|
|
var conductor_diameter_slider = document.getElementById("conductor_diameter_slider");
|
|
var loop_turns_slider = document.getElementById("loop_turns_slider");
|
|
var loop_spacing_slider = document.getElementById("loop_spacing_slider");
|
|
var transmit_power_slider = document.getElementById("transmit_power_slider");
|
|
var metric_radio = document.getElementById("metric_radio");
|
|
var imperial_radio = document.getElementById("imperial_radio");
|
|
var shape_radio = document.getElementById("shape_radio");
|
|
var external_losses_slider = document.getElementById("external_losses_slider");
|
|
|
|
const params_to_sliders = {
|
|
loop_diameter: loop_diameter_slider,
|
|
conductor_diameter: conductor_diameter_slider,
|
|
loop_turns: loop_turns_slider,
|
|
loop_spacing: loop_spacing_slider,
|
|
transmit_power: transmit_power_slider,
|
|
external_losses: external_losses_slider,
|
|
}
|
|
const sliders_to_params = Object.entries(params_to_sliders).map(([k, v]) => [v, k]);
|
|
|
|
const params_to_radio_names = {
|
|
unit: "unit_radio",
|
|
metal: "metal_radio",
|
|
shape: "shape_radio",
|
|
}
|
|
const radio_names_to_params = Object.entries(params_to_radio_names).map(([k, v]) => [v, k]);
|
|
|
|
// If there's a query param in the URL, set each slider's value to the respective params.
|
|
for (const [key, value] of new URLSearchParams(window.location.search)) {
|
|
var slider = params_to_sliders[key];
|
|
if (slider) {
|
|
slider.value = value;
|
|
}
|
|
|
|
var radio_name = params_to_radio_names[key];
|
|
if (radio_name) {
|
|
// Find all radio elements with this name, set each checked status to the current value.
|
|
var radios = document.getElementsByName(radio_name);
|
|
for (var i = 0; i < radios.length; i++) {
|
|
radios[i].checked = (radios[i].value == value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Function to call after a successful recalculation
|
|
const updateURL = function() {
|
|
const usp = new URLSearchParams();
|
|
for (const [slider, param] of sliders_to_params) {
|
|
if (slider == null) continue;
|
|
var value = slider.value;
|
|
usp.set(param, value);
|
|
}
|
|
|
|
for (const [radio_name, param] of radio_names_to_params) {
|
|
var radio = document.querySelector(`input[type=radio][name=${radio_name}]:checked`);
|
|
usp.set(param, radio.value);
|
|
}
|
|
|
|
var new_url = new URL(window.location.href);
|
|
new_url.search = usp;
|
|
window.history.replaceState(null, null, new_url);
|
|
}
|
|
|
|
function getUnits() {
|
|
var unit_radio = document.getElementsByName("unit_radio");
|
|
for(var i = 0; i < unit_radio.length; i++) {
|
|
if(unit_radio[i].checked == true) {
|
|
return unit_radio[i].value;
|
|
}
|
|
}
|
|
return "null";
|
|
}
|
|
|
|
function getMetal() {
|
|
var metal_radio = document.getElementsByName("metal_radio");
|
|
for(var i = 0; i < metal_radio.length; i++) {
|
|
if(metal_radio[i].checked == true) {
|
|
return metal_radio[i].value;
|
|
}
|
|
}
|
|
return "null";
|
|
}
|
|
|
|
function getShape() {
|
|
var shape_radio = document.getElementsByName("shape_radio");
|
|
for(var i = 0; i < shape_radio.length; i++) {
|
|
if(shape_radio[i].checked == true) {
|
|
return shape_radio[i].value;
|
|
}
|
|
}
|
|
return "null";
|
|
}
|
|
|
|
// Global variables:
|
|
var units = getUnits();
|
|
var metal = getMetal(); // Default metal is copper
|
|
var conductivity = 58e6; // Default is annealed copper
|
|
var shape = getShape();
|
|
var loop_turns = 1;
|
|
var loop_mode = "series"; // Series or parallel. When loop_turns_slider.value == 0, loop_turns is 2 and loop_mode is "parallel"
|
|
const mu0 = Math.PI * 4e-7;
|
|
|
|
var inductance = 0.0;
|
|
var area = 0.0; // Loop area in square meters.
|
|
var perimeter = 0.0; // Perimeter of a single turn of the main loop
|
|
var loop_capacitance = 0.0; // Effective capacitance of a single or multi-turn loop
|
|
var srf = 0.0; // Self-resonant frequency SRF
|
|
var conductor_length = 0.0; // Total conductor length
|
|
var R_ext = 0.0; // External losses due to capacitor resistance and ground effects, in ohms
|
|
|
|
const proximityResistance = {
|
|
// From G. S. Smith, "Radiation Efficiency of Electrically Small Multiturn Loop Antennas", IEEE Trans Antennas Propagation, September 1972
|
|
// 0 - this is the corresponding x-axis value. 1 - single loop adds zero to proximity resistance. Others measured empirically.
|
|
0:[ 1.1, 1.15, 1.20, 1.25, 1.30, 1.40, 1.50, 1.60, 1.70, 1.80, 1.90, 2.00, 2.20, 2.40, 2.50, 2.60, 2.80, 3.00, 3.50, 4.00],
|
|
1:[0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000],
|
|
2:[0.299, 0.284, 0.268, 0.254, 0.240, 0.214, 0.191, 0.173, 0.155, 0.141, 0.128, 0.116, 0.098, 0.032, 0.077, 0.071, 0.061, 0.054, 0.040, 0.031],
|
|
3:[0.643, 0.580, 0.531, 0.491, 0.455, 0.395, 0.346, 0.305, 0.270, 0.241, 0.216, 0.195, 0.161, 0.135, 0.124, 0.114, 0.098, 0.085, 0.062, 0.048],
|
|
4:[0.996, 0.868, 0.777, 0.704, 0.644, 0.564, 0.470, 0.408, 0.353, 0.316, 0.281, 0.252, 0.205, 0.170, 0.156, 0.144, 0.123, 0.106, 0.077, 0.058],
|
|
5:[1.347, 1.142, 1.002, 0.896, 0.809, 0.674, 0.572, 0.492, 0.428, 0.375, 0.332, 0.295, 0.239, 0.197, 0.180, 0.165, 0.141, 0.121, 0.087, 0.066],
|
|
6:[1.689, 1.400, 1.210, 1.068, 0.956, 0.784, 0.658, 0.561, 0.485, 0.423, 0.372, 0.330, 0.265, 0.217, 0.198, 0.182, 0.154, 0.133, 0.095, 0.072],
|
|
7:[2.020, 1.693, 1.401, 1.224, 1.086, 0.880, 0.732, 0.620, 0.532, 0.462, 0.405, 0.358, 0.286, 0.234, 0.213, 0.195, 0.165, 0.142, 0.101, 0.076],
|
|
8:[2.340, 1.872, 1.577, 1.365, 1.203, 0.965, 0.796, 0.670, 0.573, 0.495, 0.433, 0.392, 0.304, 0.247, 0.225, 0.206, 0.174, 0.150, 0.106, 0.080]
|
|
};
|
|
|
|
var frequencies = [];
|
|
|
|
function updateFrequencies() {
|
|
const hamFrequencies = [
|
|
0.1357, 0.1378, 0.472, 0.479, 1.8, 1.875, 3.5, 3.8, 5.3, 5.4, 7.0, 7.3, 10.1, 10.15, 14.0, 14.35, 18.068, 18.168, 21.0, 21.45, 24.89, 24.99, 26.965, 27.855, 28.0, 29.7, 35.0, 40.0, 45.0, 50.0, 52.0, 54.0, 55.0, 60.0, 65.0, 69.9, 70.5, 80.0, 90.0,
|
|
100.0, 110.0, 120.0, 130.0, 140.0, 144.0, 146.0, 148.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0, 210.0, 219.0, 222.0, 225.0, 230.0, 240.0, 250.0, 275.0, 300.0, 325.0, 350.0, 375.0, 400.0, 420.0, 430.0, 440.0, 450.0
|
|
];
|
|
|
|
frequencies = [];
|
|
hamFrequencies.forEach(freq => {
|
|
const wavelength = 3e8 / (freq * 1e6);
|
|
const l = (Math.PI * loop_diameter_slider.value) / wavelength;
|
|
if ((l <= 0.30) && ((freq * 1e6) < srf)) {
|
|
frequencies.push(freq);
|
|
}
|
|
});
|
|
}
|
|
|
|
function setGlobals() {
|
|
loop_turns = loop_turns_slider.value >= 1 ? parseInt(loop_turns_slider.value) : 2;
|
|
loop_mode = loop_turns_slider.value >= 1 ? "series" : "parallel";
|
|
|
|
units = getUnits();
|
|
shape = getShape();
|
|
metal = getMetal();
|
|
if(metal == "Cu") {
|
|
conductivity = 58e6; // Default is annealed copper
|
|
} else if(metal == "Al") {
|
|
conductivity = 35e6;
|
|
}
|
|
|
|
area = getArea();
|
|
perimeter = getPerimeter();
|
|
|
|
inductance = getInductance();
|
|
loop_capacitance = ((loop_turns > 1) && (loop_mode == "series")) ? multiloopCapacitance() : (2.69e-12 * perimeter);
|
|
srf = calculateSRF();
|
|
conductor_length = (loop_turns_slider.value > 1)
|
|
? ((((perimeter* loop_turns) ** 2.0) + ((loop_spacing_slider.value * conductor_diameter_slider.value * 1e-3 * loop_turns) ** 2.0)) ** 0.5)
|
|
: (perimeter* loop_turns);
|
|
R_ext = external_losses_slider.value * 0.001;
|
|
}
|
|
|
|
// Returns the loop area in square meters:
|
|
function getArea() {
|
|
var l_area = 0.0;
|
|
const width = loop_diameter_slider.value;
|
|
if(shape == "circle") {
|
|
l_area = 3.141592 * (0.5 * width)**2;
|
|
} else
|
|
if(shape == "octagon") {
|
|
const r = 0.4142135 * width;
|
|
l_area = 4.8284 * r**2.0;
|
|
} else
|
|
if(shape == "hexagon") {
|
|
const r = 0.5 * width;
|
|
l_area = 3.4641 * r**2.0;
|
|
} else
|
|
if(shape == "square") {
|
|
l_area = width **2.0;
|
|
}
|
|
return l_area;
|
|
}
|
|
|
|
// Returns the perimeter length in meters:
|
|
function getPerimeter() {
|
|
var l_perimeter = 0.0;
|
|
const width = loop_diameter_slider.value;
|
|
if(shape == "circle") {
|
|
l_perimeter = 3.141592 * width;
|
|
} else
|
|
if(shape == "octagon") {
|
|
const r = 0.4142135 * width;
|
|
l_perimeter = 8 * r;
|
|
} else
|
|
if(shape == "hexagon") {
|
|
const r = 0.5 * width;
|
|
const t = 2.0 * r * 0.57735;
|
|
l_perimeter = 6 * t;
|
|
} else
|
|
if(shape == "square") {
|
|
l_perimeter = 4 * width;
|
|
}
|
|
return l_perimeter;
|
|
}
|
|
|
|
// Calculate the inductance of the coil. For single turn loops, use standard inductance equation. For multi-turn, use Nagaoka correction.
|
|
function getInductance() {
|
|
const loop_diameter_meters = loop_diameter_slider.value;
|
|
const cond_diameter_meters = conductor_diameter_slider.value * 1e-3;
|
|
const spacing_ratio = loop_spacing_slider.value;
|
|
|
|
const a_coil_radius = loop_diameter_meters * 0.5;
|
|
const coil_length = cond_diameter_meters * spacing_ratio * loop_turns;
|
|
const N = loop_turns;
|
|
const l = (N>1) ? (coil_length * 100.0) : (cond_diameter_meters * 100.0); // coil length in cm
|
|
|
|
var retval = 0.0;
|
|
|
|
if(shape == "circle") {
|
|
if(loop_turns > 1) {
|
|
retval = (loop_turns**2.0) * mu0 * Math.PI * (a_coil_radius**2.0) * nagaokaCoefficient() / coil_length;
|
|
} else {
|
|
const b_conductor_radius = cond_diameter_meters * 0.5;
|
|
retval = mu0 * a_coil_radius * (Math.log(8.0 * a_coil_radius / b_conductor_radius) - 2.0);
|
|
}
|
|
} else
|
|
if(shape == "octagon") {
|
|
const s = (100.0 * loop_diameter_meters) * 0.414213; // side length in cm
|
|
const bOn2r = l / (1.306563*s);
|
|
//retval = 1e-6 * 0.016 * (N**2) * s * ( Math.log((2.613*s*N)/((N+1)*l)) + 0.75143 + ((0.07153*(N+1)*l) / (s*N))); // ARRL Antennas Book 17th Ed
|
|
retval = 1e-6 * 0.016 * (N**2) * s * ( Math.log(1.0/bOn2r) + 0.75143 + 0.18693*bOn2r + 0.11969*bOn2r**2 - 0.08234*bOn2r**4); // F W Grover p753
|
|
//retval = 1e-6 * 0.016 * (N**2) * s * ( Math.log(s/l) + 1.711976 + 0.075143*(l/s) + 0.017528*(l/s)**2 - 0.001766*(l/s)**4);
|
|
} else
|
|
if(shape == "hexagon") {
|
|
const s = (100.0 * loop_diameter_meters) * 0.57735; // side length in cm
|
|
const bOn2r = l / (loop_diameter_meters * 115.470);
|
|
//retval = 1e-6 * 0.012 * (N**2) * s * ( Math.log((2.0*s*N)/((N+1)*l)) + 0.65533 + ((0.1348*(N+1)*l) / (s*N))); // ARRL Antennas Book 17th Ed
|
|
retval = 1e-6 * 0.012 * (N**2) * s * ( Math.log(1.0/bOn2r) + 0.65533 + 0.26960*bOn2r + 0.07736*bOn2r**2 - 0.05504*bOn2r**4); // F W Grover p753
|
|
} else
|
|
if(shape == "square") {
|
|
const s = (100.0 * loop_diameter_meters); // side length in cm
|
|
const bOn2r = l / (loop_diameter_meters * 141.4214);
|
|
//retval = 1e-6 * 0.008 * (N**2) * s * ( Math.log((1.4142*s*N)/((N+1)*l)) + 0.37942 + ((0.3333*(N+1)*l)/(s*N))); // ARRL Antennas Book 17th Ed
|
|
retval = 1e-6 * 0.008 * (N**2) * s * ( Math.log(1.0/bOn2r) + 0.37942 + 0.47140*bOn2r - 0.014298*bOn2r**2 - 0.02904*bOn2r**4); // F W Grover p753
|
|
}
|
|
|
|
if(loop_mode == "parallel") {
|
|
// then N==2, so divide by 4 to go from serial to parallel inductance:
|
|
retval *= 0.25;
|
|
}
|
|
|
|
return retval; // In Henries
|
|
}
|
|
|
|
function radiationResistance(frequency) {
|
|
const wavelength = 299792458.0 / frequency;
|
|
var retval = 0.0;
|
|
|
|
if(shape == "circle") {
|
|
const k = 20.0 * (Math.PI ** 2.0);
|
|
const l = (Math.PI * loop_diameter_slider.value) / wavelength;
|
|
retval = (loop_turns ** 2.0) * k * (l ** 4.0);
|
|
} else {
|
|
retval = (31171.0 * loop_turns**2.0 * area**2.0) / (wavelength**4.0);
|
|
}
|
|
if(loop_mode == "parallel") {
|
|
retval *= 0.25;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
function calculateRadiationResistance() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const rr = radiationResistance(freq * 1e6);
|
|
retval.push({x:freq, y:rr});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function inductiveReactance(frequency) {
|
|
//const inductance = getInductance();
|
|
const reactance = 2.0 * Math.PI * frequency * inductance;
|
|
return reactance;
|
|
}
|
|
|
|
function calculateInductiveReactance() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const reactance = inductiveReactance(freq * 1e6);
|
|
retval.push({x:freq, y:reactance});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function nagaokaCoefficient() {
|
|
// From Knight's 2016 paper on coil self-resonance, attributed to Wheeler's 1982 eqn as modified by Bob Weaver
|
|
const c_spacing = 1e-3 * loop_spacing_slider.value * conductor_diameter_slider.value;
|
|
const x = loop_diameter_slider.value / (c_spacing * loop_turns);
|
|
const zk = 2.0 / (Math.PI * x);
|
|
const k0 = 1.0 / (Math.log(8.0 / Math.PI) - 0.5);
|
|
const k2 = 24.0 / (3.0 * Math.PI**2 - 16.0);
|
|
const w = -0.47 / (0.755 + x)**1.44;
|
|
const p = k0 + 3.437/x + k2/x**2 + w;
|
|
return zk * (Math.log(1 + 1/zk) + 1/p);
|
|
}
|
|
|
|
function ctdw(ff, ei, ex) {
|
|
// From Knight's 2016 paper
|
|
const kL = nagaokaCoefficient();
|
|
const kct = 1.0/kL - 1.0;
|
|
return 11.27350207 * ex * ff * (1.0 + kct * (1.0 + ei/ex) / 2.0);
|
|
}
|
|
|
|
function ciae(ff, ei, ex) {
|
|
// From Knight's 2016 paper
|
|
return 17.70837564 * (ei+ex) / Math.log(1.0 + Math.PI**2 * ff);
|
|
}
|
|
|
|
// Calculates the effective capacitance of a multi-turn loop based on the work of Knight from his 2016 paper:
|
|
function multiloopCapacitance() {
|
|
const e0 = 8.854187e-12;
|
|
const h = 1e-3 * loop_spacing_slider.value * conductor_diameter_slider.value;
|
|
const ei = 1.0; // Assume internal epsilon is air (or free-space)
|
|
const ex = 1.0; // Assume external epsilon is air (or free-space)
|
|
const solenoid_length = loop_turns * h;
|
|
const ff = solenoid_length / loop_diameter_slider.value;
|
|
|
|
// How much longer is the perimeter compared to the circumference if it were circular:
|
|
const shape_factor = perimeter / (Math.PI * loop_diameter_slider.value);
|
|
|
|
var l_multiloop_capacitance = 1e-12 * shape_factor * (ctdw(ff, ei, ex) / Math.sqrt(1 - h**2 / loop_diameter_slider.value**2) + ciae(ff, ei, ex)) * loop_diameter_slider.value;
|
|
return l_multiloop_capacitance; // in Farads
|
|
}
|
|
/*
|
|
function singleloopCapacitance() {
|
|
var retval = 2.69 * perimeter;
|
|
return (retval*1e-12); // in Farads
|
|
}
|
|
*/
|
|
|
|
function tuningCapacitance(frequency) {
|
|
// frequency is in Hertz
|
|
const reactance = inductiveReactance(frequency);
|
|
const capacitance = 1e12 * ((1.0 / (2.0 * Math.PI * frequency * reactance)) - loop_capacitance);
|
|
return capacitance; // in picofarads
|
|
}
|
|
|
|
function calculateTuningCapacitor() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const capacitor = tuningCapacitance(freq * 1e6);
|
|
retval.push({x:freq, y:capacitor});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function getProximityResFromSpacing(spacing_ratio) {
|
|
// Use the proximityResistance look-up table and interpolate values depending on the spacing ratio and the number of turns.
|
|
var retval = 0.0;
|
|
var i = 0;
|
|
for (i = 0; i < (proximityResistance[0].length-1); i++) {
|
|
if(spacing_ratio <= proximityResistance[0][i+1]) {
|
|
// Linear interpolation between empirical proximity resistance values:
|
|
retval = (((spacing_ratio - proximityResistance[0][i]) / (proximityResistance[0][i+1] - proximityResistance[0][i])
|
|
* (proximityResistance[loop_turns][i+1] - proximityResistance[loop_turns][i])) + proximityResistance[loop_turns][i]);
|
|
break;
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
function lossResistance(frequency) {
|
|
// Frequency in Hertz
|
|
const a_coil_radius = loop_diameter_slider.value * 0.5;
|
|
const b_conductor_radius = conductor_diameter_slider.value * 0.0005;
|
|
const loop_spacing_ratio = loop_spacing_slider.value;
|
|
|
|
// How much longer is the perimeter compared to the circumference if it were circular:
|
|
const shape_factor = perimeter / (Math.PI * loop_diameter_slider.value);
|
|
|
|
const k = (loop_turns * a_coil_radius / b_conductor_radius);
|
|
const Rp = getProximityResFromSpacing(loop_spacing_ratio);
|
|
const Rs = Math.sqrt(Math.PI * frequency * mu0 / conductivity);
|
|
//const R0 = (loop_turns * Rs) / (2.0 * Math.PI * b_conductor_radius);
|
|
var R_ohmic = shape_factor * k * Rs * (Rp + 1.0);
|
|
if(loop_mode == "parallel") {
|
|
R_ohmic *= 0.25;
|
|
}
|
|
//const R_ohmic = k * Rs * (Rp / R0 + 1.0);
|
|
return R_ohmic;
|
|
}
|
|
|
|
function calculateLossResistance() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const R_ohmic = lossResistance(freq * 1e6);
|
|
retval.push({x:freq, y:R_ohmic});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
// Returns skin-depth in micrometers:
|
|
function calculateSkinDepth() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const skin_depth = 1e6 * Math.sqrt(1e-6/(conductivity * Math.PI * freq * mu0));
|
|
retval.push({x:freq, y:skin_depth});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function calculateEfficiencyFactor() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const R_ohmic = lossResistance(freq * 1e6);
|
|
const R_rad = radiationResistance(freq * 1e6);
|
|
const efficiency = 100.0 * R_rad / (R_rad + R_ohmic + R_ext);
|
|
retval.push({x:freq, y:efficiency});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function qualityFactor(frequency) {
|
|
const Xl = inductiveReactance(frequency);
|
|
const Rl = lossResistance(frequency);
|
|
const Rr = radiationResistance(frequency);
|
|
const Q = Xl / (Rl + Rr + R_ext);
|
|
return Q;
|
|
}
|
|
|
|
function calculateQualityFactor() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const Q = qualityFactor(freq * 1e6);
|
|
retval.push({x:freq, y:Q});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function bandwidth(frequency) {
|
|
const Q = qualityFactor(frequency);
|
|
const bw = frequency * 1e-3 / Q; // in kiloHertz, remember that frequency comes in as Hz. Conversion between Hz and kHz is why the 1e-3 exists.
|
|
return bw;
|
|
}
|
|
|
|
function calculateBandwidth() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const bw = bandwidth(freq * 1e6);
|
|
retval.push({x:freq, y:bw});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function capacitorVoltage(frequency) {
|
|
const Vcap = Math.sqrt(transmit_power_slider.value * inductiveReactance(frequency) * qualityFactor(frequency));
|
|
return Vcap;
|
|
}
|
|
|
|
function calculateCapacitorVoltage() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const Vcap = 0.001 * capacitorVoltage(freq * 1e6);
|
|
retval.push({x:freq, y:Vcap});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function circulatingCurrent(frequency) {
|
|
const cc = Math.sqrt(transmit_power_slider.value * qualityFactor(frequency) / inductiveReactance(frequency));
|
|
return cc;
|
|
}
|
|
|
|
function calculateCirculatingCurrent() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const cc = circulatingCurrent(freq * 1e6);
|
|
retval.push({x:freq, y:cc});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function calculateAntennaSize() {
|
|
var retval = [];
|
|
frequencies.forEach(freq => {
|
|
const lambda = 3e8 / (freq*1e6);
|
|
const size = perimeter / lambda; // size along the perimeter
|
|
// const size = conductor_length / lambda;
|
|
retval.push({x:freq, y:size});
|
|
});
|
|
return retval;
|
|
}
|
|
|
|
function calculateSRF() {
|
|
// According to Knight (2016), SRF for a single coil is equivalent to the circumference being equivalent to a half-wave dipole.
|
|
const inductance = getInductance();
|
|
return (1.0 / (2.0 * Math.PI * ((inductance * loop_capacitance) ** 0.5)));
|
|
}
|
|
|
|
function updateUnits() {
|
|
setGlobals();
|
|
drawFrontDesign();
|
|
drawSideDesign();
|
|
updateURL();
|
|
}
|
|
|
|
function updateAll() {
|
|
setGlobals();
|
|
drawFrontDesign();
|
|
drawSideDesign();
|
|
updateFrequencies();
|
|
myChart.data.datasets[0].data = calculateTuningCapacitor();
|
|
myChart.data.datasets[1].data = calculateCapacitorVoltage();
|
|
myChart.data.datasets[2].data = calculateBandwidth();
|
|
myChart.data.datasets[3].data = calculateEfficiencyFactor();
|
|
myChart.data.datasets[4].data = calculateRadiationResistance();
|
|
myChart.data.datasets[5].data = calculateLossResistance();
|
|
myChart.data.datasets[6].data = calculateInductiveReactance();
|
|
myChart.data.datasets[7].data = calculateQualityFactor();
|
|
myChart.data.datasets[8].data = calculateCirculatingCurrent();
|
|
myChart.data.datasets[9].data = calculateAntennaSize();
|
|
myChart.data.datasets[10].data = calculateSkinDepth();
|
|
myChart.update();
|
|
updateURL();
|
|
}
|
|
|
|
metric_radio.oninput = function() {
|
|
updateUnits();
|
|
}
|
|
|
|
imperial_radio.oninput = function() {
|
|
updateUnits();
|
|
}
|
|
|
|
copper_radio.oninput = function() {
|
|
updateAll();
|
|
}
|
|
|
|
aluminium_radio.oninput = function() {
|
|
updateAll();
|
|
}
|
|
|
|
circle_radio.oninput = function() {
|
|
updateAll();
|
|
}
|
|
|
|
oct_radio.oninput = function() {
|
|
updateAll();
|
|
}
|
|
|
|
hex_radio.oninput = function() {
|
|
updateAll();
|
|
}
|
|
|
|
square_radio.oninput = function() {
|
|
updateAll();
|
|
}
|
|
|
|
// Specify fonts for changing parameters controlled by the sliders:
|
|
const normal_font = "12px arial";
|
|
const emphasis_font = "bold 14px arial";
|
|
const emphasis_delay = 1200;
|
|
|
|
const normal_width = 1;
|
|
const emphasis_width = 3;
|
|
|
|
var loop_dia_timer_handler = 0;
|
|
var loop_dia_font = normal_font;
|
|
var loop_dia_thickness = normal_width;
|
|
|
|
loop_diameter_slider.oninput = function() {
|
|
if(loop_dia_timer_handler == 0) {
|
|
loop_dia_font = emphasis_font;
|
|
loop_dia_thickness = emphasis_width;
|
|
loop_dia_timer_handler = setTimeout(function(){
|
|
loop_dia_font = normal_font;
|
|
drawFrontDesign();
|
|
loop_dia_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
} else {
|
|
clearTimeout(loop_dia_timer_handler);
|
|
loop_dia_timer_handler = setTimeout(function(){
|
|
loop_dia_font = normal_font;
|
|
loop_dia_thickness = normal_width;
|
|
drawFrontDesign();
|
|
loop_dia_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
}
|
|
|
|
updateAll();
|
|
}
|
|
|
|
var cond_dia_timer_handler = 0;
|
|
var cond_dia_font = normal_font;
|
|
var cond_dia_thickness = normal_width;
|
|
|
|
conductor_diameter_slider.oninput = function() {
|
|
if(cond_dia_timer_handler == 0) {
|
|
cond_dia_font = emphasis_font;
|
|
cond_dia_thickness = emphasis_width;
|
|
cond_dia_timer_handler = setTimeout(function(){
|
|
cond_dia_font = normal_font;
|
|
drawFrontDesign();
|
|
cond_dia_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
} else {
|
|
clearTimeout(cond_dia_timer_handler);
|
|
cond_dia_timer_handler = setTimeout(function(){
|
|
cond_dia_font = normal_font;
|
|
cond_dia_thickness = normal_width;
|
|
drawFrontDesign();
|
|
cond_dia_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
}
|
|
|
|
updateAll();
|
|
}
|
|
|
|
var turns_timer_handler = 0;
|
|
var turns_font = normal_font;
|
|
|
|
loop_turns_slider.oninput = function() {
|
|
if(turns_timer_handler == 0) {
|
|
turns_font = emphasis_font;
|
|
turns_timer_handler = setTimeout(function(){
|
|
turns_font = normal_font;
|
|
drawSideDesign();
|
|
turns_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
} else {
|
|
clearTimeout(turns_timer_handler);
|
|
turns_timer_handler = setTimeout(function(){
|
|
turns_font = normal_font;
|
|
drawSideDesign();
|
|
turns_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
}
|
|
|
|
updateAll();
|
|
}
|
|
|
|
var spacing_timer_handler = 0;
|
|
var spacing_font = normal_font;
|
|
|
|
loop_spacing_slider.oninput = function() {
|
|
if(spacing_timer_handler == 0) {
|
|
spacing_font = emphasis_font;
|
|
spacing_timer_handler = setTimeout(function(){
|
|
spacing_font = normal_font;
|
|
drawSideDesign();
|
|
spacing_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
} else {
|
|
clearTimeout(spacing_timer_handler);
|
|
spacing_timer_handler = setTimeout(function(){
|
|
spacing_font = normal_font;
|
|
drawSideDesign();
|
|
spacing_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
}
|
|
|
|
updateAll();
|
|
}
|
|
|
|
var tx_timer_handler = 0;
|
|
var tx_font = normal_font;
|
|
|
|
transmit_power_slider.oninput = function() {
|
|
if(tx_timer_handler == 0) {
|
|
tx_font = emphasis_font;
|
|
tx_timer_handler = setTimeout(function(){
|
|
tx_font = normal_font;
|
|
drawFrontDesign();
|
|
tx_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
} else {
|
|
clearTimeout(tx_timer_handler);
|
|
tx_timer_handler = setTimeout(function(){
|
|
tx_font = normal_font;
|
|
drawFrontDesign();
|
|
tx_timer_handler = 0;
|
|
}, emphasis_delay);
|
|
}
|
|
|
|
setGlobals();
|
|
drawFrontDesign();
|
|
myChart.data.datasets[1].data = calculateCapacitorVoltage();
|
|
myChart.data.datasets[8].data = calculateCirculatingCurrent();
|
|
myChart.update();
|
|
updateURL();
|
|
}
|
|
|
|
var external_losses_handler = 0;
|
|
var external_losses_font = normal_font;
|
|
|
|
external_losses_slider.oninput = function() {
|
|
if(external_losses_handler == 0) {
|
|
external_losses_font = emphasis_font;
|
|
external_losses_handler = setTimeout(function(){
|
|
external_losses_font = normal_font;
|
|
drawFrontDesign();
|
|
external_losses_handler = 0;
|
|
}, emphasis_delay);
|
|
} else {
|
|
clearTimeout(external_losses_handler);
|
|
external_losses_handler = setTimeout(function(){
|
|
external_losses_font = normal_font;
|
|
drawFrontDesign();
|
|
external_losses_handler = 0;
|
|
}, emphasis_delay);
|
|
}
|
|
|
|
setGlobals();
|
|
drawFrontDesign();
|
|
myChart.data.datasets[1].data = calculateCapacitorVoltage();
|
|
myChart.data.datasets[2].data = calculateBandwidth();
|
|
myChart.data.datasets[3].data = calculateEfficiencyFactor();
|
|
myChart.data.datasets[7].data = calculateQualityFactor();
|
|
myChart.data.datasets[8].data = calculateCirculatingCurrent();
|
|
myChart.update();
|
|
updateURL();
|
|
}
|
|
|
|
window.onresize = function() {
|
|
myChart.resize();
|
|
//myChart.update();
|
|
drawFrontDesign();
|
|
drawSideDesign();
|
|
// console.log("resize!");
|
|
}
|
|
|
|
window.onorientationchange = function() {
|
|
//myChart.resize();
|
|
//myChart.update();
|
|
drawFrontDesign();
|
|
drawSideDesign();
|
|
}
|
|
|
|
window.onbeforeprint = function() {
|
|
console.log("onbeforeprint");
|
|
//myChart.resize();
|
|
drawFrontDesign();
|
|
drawSideDesign();
|
|
}
|
|
|
|
const afront_canvas = document.getElementById("antennaFront2D");
|
|
const fctx = afront_canvas.getContext('2d');
|
|
|
|
function drawFrontDesign() {
|
|
const win_width = document.getElementById("antenna-front-container").offsetWidth;
|
|
const win_height = document.getElementById("antenna-front-container").offsetHeight;
|
|
afront_canvas.width = win_width-2;
|
|
afront_canvas.height = win_height-2;
|
|
|
|
fctx.clearRect(0, 0, win_width, win_height);
|
|
const loop_radius = win_width < win_height ? 0.32 * win_width : 0.32 * win_height;
|
|
const cond_radius = conductor_diameter_slider.value / 12;
|
|
const loopx = win_width/2;
|
|
const loopy = win_height/2;
|
|
|
|
if(shape == "circle") {
|
|
// Draw loop:
|
|
fctx.beginPath();
|
|
fctx.arc(loopx, loopy, loop_radius + cond_radius, -0.5 * Math.PI + 0.025, -0.5 * Math.PI - 0.025, false);
|
|
fctx.arc(loopx, loopy, loop_radius - cond_radius, -0.5 * Math.PI - 0.025, -0.5 * Math.PI + 0.025, true);
|
|
fctx.closePath();
|
|
fctx.fill();
|
|
|
|
// Draw variable capacitor:
|
|
fctx.lineWidth = 3;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx - 3, loopy - loop_radius + 3*cond_radius);
|
|
fctx.moveTo(loopx + 3, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx + 3, loopy - loop_radius + 3*cond_radius);
|
|
fctx.moveTo(loopx - 12, loopy - loop_radius + 3*cond_radius);
|
|
fctx.lineTo(loopx + 12, loopy - loop_radius - 3*cond_radius);
|
|
/*
|
|
fctx.moveTo(loopx + 8, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx + 14, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx + 14, loopy - loop_radius - 3*cond_radius + 6);
|
|
*/
|
|
fctx.stroke();
|
|
|
|
// Draw coupling loop:
|
|
fctx.lineWidth = 2;
|
|
fctx.beginPath();
|
|
fctx.arc(loopx, loopy + (loop_radius - loop_radius/5) - cond_radius , loop_radius/5, 0, 2*Math.PI, true);
|
|
fctx.stroke();
|
|
|
|
// Draw conductor diameter arrow:
|
|
fctx.lineWidth = cond_dia_thickness;
|
|
fctx.beginPath();
|
|
var p1x = loopx + 0.45 * (loop_radius - cond_radius);
|
|
var p1y = loopy + 0.45 * (loop_radius - cond_radius);
|
|
var p2x = loopx + 0.707 * (loop_radius - cond_radius);
|
|
var p2y = loopy + 0.707 * (loop_radius - cond_radius);
|
|
var p3x = loopx + 0.707 * (loop_radius - cond_radius) - 3*cond_radius;
|
|
var p3y = loopy + 0.707 * (loop_radius - cond_radius);
|
|
var p4x = loopx + 0.707 * (loop_radius - cond_radius);
|
|
var p4y = loopy + 0.707 * (loop_radius - cond_radius) - 3*cond_radius;
|
|
fctx.moveTo(win_width-p1x, p1y);
|
|
fctx.lineTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
|
|
p1x = loopx + 0.9 * (loop_radius + cond_radius);
|
|
p1y = loopy + 0.9 * (loop_radius + cond_radius);
|
|
p2x = loopx + 0.707 * (loop_radius + cond_radius);
|
|
p2y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p3x = loopx + 0.707 * (loop_radius + cond_radius) + 3*cond_radius;
|
|
p3y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p4x = loopx + 0.707 * (loop_radius + cond_radius);
|
|
p4y = loopy + 0.707 * (loop_radius + cond_radius) + 3*cond_radius;
|
|
fctx.moveTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.stroke();
|
|
fctx.lineWidth = normal_width;
|
|
|
|
fctx.font = normal_font;
|
|
fctx.textAlign = "left";
|
|
fctx.fillText(metal, loopx + 0.9 * (loop_radius + cond_radius) + 3, loopy + 0.9 * (loop_radius + cond_radius));
|
|
} else
|
|
if(shape == "octagon") {
|
|
// Draw octagon:
|
|
//const width = loop_diameter_slider.value;
|
|
const a = 0.4142135 * loop_radius * 2.0;
|
|
const circumradius = 1.30656296 * a;
|
|
fctx.lineWidth = 2 * cond_radius;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - loop_radius);
|
|
for(var i=0; i<8; i++) {
|
|
fctx.lineTo(loopx - circumradius * Math.sin(Math.PI * (i/4.0 + 1.0/8)), loopy - circumradius * Math.cos(Math.PI * (i/4.0 + 1.0/8)));
|
|
}
|
|
fctx.lineTo(loopx + 3, loopy - loop_radius);
|
|
fctx.stroke();
|
|
|
|
// Draw variable capacitor:
|
|
fctx.lineWidth = 3;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx - 3, loopy - loop_radius + 3*cond_radius);
|
|
fctx.moveTo(loopx + 3, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx + 3, loopy - loop_radius + 3*cond_radius);
|
|
fctx.moveTo(loopx - 12, loopy - loop_radius + 3*cond_radius);
|
|
fctx.lineTo(loopx + 12, loopy - loop_radius - 3*cond_radius);
|
|
fctx.stroke();
|
|
|
|
// Draw coupling loop:
|
|
fctx.lineWidth = 2;
|
|
fctx.beginPath();
|
|
fctx.arc(loopx, loopy + (loop_radius - loop_radius/5) - cond_radius , loop_radius/5, 0, 2*Math.PI, true);
|
|
fctx.stroke();
|
|
|
|
fctx.font = normal_font;
|
|
fctx.save();
|
|
fctx.translate(loopx - loop_radius - cond_radius - 8, loopy);
|
|
fctx.rotate(-Math.PI * 0.5);
|
|
fctx.textAlign = "center";
|
|
const s = (100.0 * loop_diameter_slider.value) * 0.414213; // side length in cm
|
|
if(units == "metric") {
|
|
fctx.fillText(s.toPrecision(3).toString() + " cm", 0, 0);
|
|
} else {
|
|
fctx.fillText((s*0.03281).toPrecision(3).toString() + "\'", 0, 0);
|
|
}
|
|
fctx.restore();
|
|
|
|
// Draw conductor diameter arrow:
|
|
fctx.lineWidth = cond_dia_thickness;
|
|
fctx.beginPath();
|
|
var p1x = loopx + 0.45 * (loop_radius - cond_radius);
|
|
var p1y = loopy + 0.45 * (loop_radius - cond_radius);
|
|
var p2x = loopx + 0.707 * (loop_radius - cond_radius);
|
|
var p2y = loopy + 0.707 * (loop_radius - cond_radius);
|
|
var p3x = loopx + 0.707 * (loop_radius - cond_radius) - 3*cond_radius;
|
|
var p3y = loopy + 0.707 * (loop_radius - cond_radius);
|
|
var p4x = loopx + 0.707 * (loop_radius - cond_radius);
|
|
var p4y = loopy + 0.707 * (loop_radius - cond_radius) - 3*cond_radius;
|
|
fctx.moveTo(win_width-p1x, p1y);
|
|
fctx.lineTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
|
|
p1x = loopx + 0.9 * (loop_radius + cond_radius);
|
|
p1y = loopy + 0.9 * (loop_radius + cond_radius);
|
|
p2x = loopx + 0.707 * (loop_radius + cond_radius);
|
|
p2y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p3x = loopx + 0.707 * (loop_radius + cond_radius) + 3*cond_radius;
|
|
p3y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p4x = loopx + 0.707 * (loop_radius + cond_radius);
|
|
p4y = loopy + 0.707 * (loop_radius + cond_radius) + 3*cond_radius;
|
|
fctx.moveTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.stroke();
|
|
fctx.lineWidth = normal_width;
|
|
|
|
fctx.font = normal_font;
|
|
fctx.textAlign = "left";
|
|
fctx.fillText(metal, p1x + 3, p1y);
|
|
} else
|
|
if(shape == "hexagon") {
|
|
// Draw hexagon:
|
|
const radius = 2.0 * loop_radius * 0.57735;
|
|
fctx.lineWidth = 2 * cond_radius;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - radius);
|
|
for(var i=1; i<6; i++) {
|
|
fctx.lineTo(loopx - radius * Math.sin(Math.PI * (i/3.0)), loopy - radius * Math.cos(Math.PI * (i/3.0)));
|
|
}
|
|
fctx.lineTo(loopx + 3, loopy - radius);
|
|
fctx.stroke();
|
|
|
|
// Draw variable capacitor:
|
|
fctx.lineWidth = 3;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - radius - 3*cond_radius);
|
|
fctx.lineTo(loopx - 3, loopy - radius + 3*cond_radius);
|
|
fctx.moveTo(loopx + 3, loopy - radius - 3*cond_radius);
|
|
fctx.lineTo(loopx + 3, loopy - radius + 3*cond_radius);
|
|
fctx.moveTo(loopx - 12, loopy - radius + 3*cond_radius);
|
|
fctx.lineTo(loopx + 12, loopy - radius - 3*cond_radius);
|
|
fctx.stroke();
|
|
|
|
// Draw coupling loop:
|
|
fctx.lineWidth = 2;
|
|
fctx.beginPath();
|
|
fctx.arc(loopx, loopy + (radius - radius/4) - cond_radius , radius/5, 0, 2*Math.PI, true);
|
|
fctx.stroke();
|
|
|
|
fctx.font = normal_font;
|
|
fctx.save();
|
|
fctx.translate(loopx - loop_radius - cond_radius - 8, loopy);
|
|
fctx.rotate(-Math.PI * 0.5);
|
|
fctx.textAlign = "center";
|
|
const s = (100.0 * loop_diameter_slider.value) * 0.57735; // side length in cm
|
|
if(units == "metric") {
|
|
fctx.fillText(s.toPrecision(3).toString() + " cm", 0, 0);
|
|
} else {
|
|
fctx.fillText((s*0.03281).toPrecision(3).toString() + "\'", 0, 0);
|
|
}
|
|
fctx.restore();
|
|
|
|
// Draw conductor diameter arrow:
|
|
fctx.lineWidth = cond_dia_thickness;
|
|
fctx.beginPath();
|
|
var p0x = loopx + 0.45 * loop_radius;
|
|
var p0y = loopy + 0.45 * loop_radius;
|
|
var p1x = loopx + loop_radius - cond_radius - 4*cond_radius;
|
|
var p1y = loopy + 0.45 * loop_radius;
|
|
var p2x = loopx + loop_radius - cond_radius;
|
|
var p2y = loopy + 0.45 * loop_radius;
|
|
var p3x = loopx + loop_radius - cond_radius - 2*cond_radius;
|
|
var p3y = loopy + 0.45 * loop_radius - 2*cond_radius;
|
|
var p4x = loopx + loop_radius - cond_radius - 2*cond_radius;
|
|
var p4y = loopy + 0.45 * loop_radius + 2*cond_radius;
|
|
fctx.moveTo(win_width-p0x, p0y);
|
|
fctx.lineTo(p0x, p0y);
|
|
//fctx.lineTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
|
|
p1x = loopx + loop_radius + cond_radius + 4*cond_radius;
|
|
//p1y = loopy + 1.0 * (loop_radius + cond_radius);
|
|
p2x = loopx + loop_radius + cond_radius;
|
|
//p2y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p3x = loopx + loop_radius + cond_radius + 2*cond_radius;
|
|
//p3y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p4x = loopx + loop_radius + cond_radius + 2*cond_radius;
|
|
//p4y = loopy + 0.707 * (loop_radius + cond_radius) + 3*cond_radius;
|
|
fctx.moveTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.stroke();
|
|
fctx.lineWidth = normal_width;
|
|
|
|
fctx.font = normal_font;
|
|
fctx.textAlign = "left";
|
|
fctx.fillText(metal, p1x + 3, p1y);
|
|
} else {
|
|
// Draw square:
|
|
const radius = 1.414 * loop_radius;
|
|
fctx.lineWidth = 2 * cond_radius;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - loop_radius);
|
|
for(var i=0; i<4; i++) {
|
|
fctx.lineTo(loopx - radius * Math.sin(Math.PI * (i/2.0 + 0.25)), loopy - radius * Math.cos(Math.PI * (i/2.0 + 0.25)));
|
|
}
|
|
fctx.lineTo(loopx + 3, loopy - loop_radius);
|
|
fctx.stroke();
|
|
|
|
// Draw variable capacitor:
|
|
fctx.lineWidth = 3;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - 3, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx - 3, loopy - loop_radius + 3*cond_radius);
|
|
fctx.moveTo(loopx + 3, loopy - loop_radius - 3*cond_radius);
|
|
fctx.lineTo(loopx + 3, loopy - loop_radius + 3*cond_radius);
|
|
fctx.moveTo(loopx - 12, loopy - loop_radius + 3*cond_radius);
|
|
fctx.lineTo(loopx + 12, loopy - loop_radius - 3*cond_radius);
|
|
fctx.stroke();
|
|
|
|
// Draw coupling loop:
|
|
fctx.lineWidth = 2;
|
|
fctx.beginPath();
|
|
fctx.arc(loopx, loopy + (loop_radius - loop_radius/5) - cond_radius , loop_radius/5, 0, 2*Math.PI, true);
|
|
fctx.stroke();
|
|
|
|
fctx.font = normal_font;
|
|
fctx.save();
|
|
fctx.translate(loopx - loop_radius - cond_radius - 8, loopy);
|
|
fctx.rotate(-Math.PI * 0.5);
|
|
fctx.textAlign = "center";
|
|
const s = (100.0 * loop_diameter_slider.value); // side length in cm
|
|
if(units == "metric") {
|
|
fctx.fillText(s.toPrecision(3).toString() + " cm", 0, 0);
|
|
} else {
|
|
fctx.fillText((s*0.03281).toPrecision(3).toString() + "\'", 0, 0);
|
|
}
|
|
fctx.restore();
|
|
|
|
// Draw conductor diameter arrow:
|
|
fctx.lineWidth = cond_dia_thickness;
|
|
fctx.beginPath();
|
|
var p0x = loopx + 0.45 * (loop_radius - cond_radius);
|
|
var p0y = loopy + 0.45 * (loop_radius - cond_radius);
|
|
var p1x = loopx + loop_radius - cond_radius - 4*cond_radius;
|
|
var p1y = loopy + 0.7 * loop_radius;
|
|
var p2x = loopx + loop_radius - cond_radius;
|
|
var p2y = loopy + 0.7 * loop_radius;
|
|
var p3x = loopx + loop_radius - cond_radius - 2*cond_radius;
|
|
var p3y = loopy + 0.7 * loop_radius - 2*cond_radius;
|
|
var p4x = loopx + loop_radius - cond_radius - 2*cond_radius;
|
|
var p4y = loopy + 0.7 * loop_radius + 2*cond_radius;
|
|
fctx.moveTo(win_width-p0x, p0y);
|
|
fctx.lineTo(p0x, p0y);
|
|
fctx.lineTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
|
|
p1x = loopx + loop_radius + cond_radius + 4*cond_radius;
|
|
//p1y = loopy + 1.0 * (loop_radius + cond_radius);
|
|
p2x = loopx + loop_radius + cond_radius;
|
|
//p2y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p3x = loopx + loop_radius + cond_radius + 2*cond_radius;
|
|
//p3y = loopy + 0.707 * (loop_radius + cond_radius);
|
|
p4x = loopx + loop_radius + cond_radius + 2*cond_radius;
|
|
//p4y = loopy + 0.707 * (loop_radius + cond_radius) + 3*cond_radius;
|
|
fctx.moveTo(p1x, p1y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.lineTo(p3x, p3y);
|
|
fctx.lineTo(p4x, p4y);
|
|
fctx.lineTo(p2x, p2y);
|
|
fctx.stroke();
|
|
fctx.lineWidth = normal_width;
|
|
|
|
fctx.font = normal_font;
|
|
fctx.textAlign = "left";
|
|
fctx.fillText(metal, p1x + 3, p1y);
|
|
}
|
|
|
|
// Draw loop diameter arrow:
|
|
fctx.lineWidth = loop_dia_thickness;
|
|
fctx.beginPath();
|
|
fctx.moveTo(loopx - loop_radius, loopy);
|
|
fctx.lineTo(loopx - loop_radius + 2*cond_radius, loopy - 2*cond_radius);
|
|
fctx.lineTo(loopx - loop_radius + 2*cond_radius, loopy + 2*cond_radius);
|
|
fctx.lineTo(loopx - loop_radius, loopy);
|
|
fctx.lineTo(loopx + loop_radius, loopy);
|
|
fctx.lineTo(loopx + loop_radius - 2*cond_radius, loopy + 2*cond_radius);
|
|
fctx.lineTo(loopx + loop_radius - 2*cond_radius, loopy - 2*cond_radius);
|
|
fctx.lineTo(loopx + loop_radius, loopy);
|
|
fctx.stroke();
|
|
fctx.lineWidth = normal_width;
|
|
|
|
// Write loop inductance:
|
|
fctx.font = normal_font;
|
|
const L = inductance * 1.0e+6;
|
|
//const L = getInductance() * 1.0e+6;
|
|
fctx.fillText("L = " + L.toPrecision(3).toString() + " \u03bcH", 8, 18);
|
|
|
|
// Write Tx power text:
|
|
fctx.font = tx_font;
|
|
fctx.fillText("Tx = " + transmit_power_slider.value + " W", 8, win_height * 0.8 + 10);
|
|
|
|
fctx.font = external_losses_font;
|
|
fctx.fillText("Re = " + R_ext.toFixed(3).toString() + " \u03A9", 8, win_height * 0.8 + 24);
|
|
|
|
// Write loop diameter symbol:
|
|
fctx.font = normal_font;
|
|
fctx.textAlign = "center";
|
|
const dia = 1.0 * loop_diameter_slider.value;
|
|
fctx.font = loop_dia_font;
|
|
if(units == "metric") {
|
|
fctx.fillText("\u2300b = " + dia.toPrecision(3).toString() + " m", loopx, loopy - 6);
|
|
} else {
|
|
fctx.fillText("\u2300b = " + (dia*3.28084).toPrecision(3).toString() + "\'", loopx, loopy - 6);
|
|
}
|
|
fctx.font = normal_font;
|
|
|
|
p1x = loopx + 0.45 * (loop_radius - cond_radius);
|
|
p1y = loopy + 0.45 * (loop_radius - cond_radius) - 5;
|
|
//fctx.textAlign = "right";
|
|
const cond_dia = 1.0 * conductor_diameter_slider.value;
|
|
fctx.textAlign = "center";
|
|
if(units == "metric") {
|
|
fctx.font = cond_dia_font;
|
|
fctx.fillText("\u2300a = " + cond_dia.toPrecision(3).toString() + " mm", loopx, p1y+1);
|
|
fctx.font = normal_font;
|
|
// Write loop area:
|
|
fctx.textAlign = "right";
|
|
fctx.fillText("A = " + area.toPrecision(3).toString() + " m\u00B2", win_width-8, 18);
|
|
|
|
// Write Tx power text:
|
|
fctx.fillText("peri = " + perimeter.toPrecision(3).toString() + " m", win_width-8, win_height * 0.8 + 24);
|
|
} else {
|
|
fctx.font = cond_dia_font;
|
|
fctx.fillText("\u2300a = " + (cond_dia/25.4).toPrecision(3).toString() + "\"", loopx, p1y+1);
|
|
fctx.font = normal_font;
|
|
// Write loop area:
|
|
fctx.textAlign = "right";
|
|
fctx.fillText("A = " + (area * 10.76391).toPrecision(3).toString() + " ft\u00B2", win_width-8, 18);
|
|
|
|
// Write Tx power text:
|
|
fctx.fillText("peri = " + (perimeter*3.28084).toPrecision(3).toString() + " ft", win_width-8, win_height * 0.8 + 24);
|
|
}
|
|
|
|
}
|
|
|
|
const aside_canvas = document.getElementById("antennaSide2D");
|
|
const sctx = aside_canvas.getContext('2d');
|
|
|
|
function drawSideDesign() {
|
|
const win_width = document.getElementById("antenna-side-container").offsetWidth;
|
|
const win_height = document.getElementById("antenna-side-container").offsetHeight;
|
|
aside_canvas.width = win_width-2;
|
|
aside_canvas.height = win_height-2;
|
|
sctx.clearRect(0, 0, win_width, win_height);
|
|
|
|
const cond_radius = conductor_diameter_slider.value / 12;
|
|
const cond_spacing = 2 * cond_radius * loop_spacing_slider.value;
|
|
const start_x = (loop_turns > 0) ? win_width/2 - loop_turns * cond_spacing * 0.5 : win_width/2;
|
|
const top_y = win_height * 0.2;
|
|
const bot_y = win_height * 0.7;
|
|
|
|
if(loop_mode == "series") {
|
|
if(loop_turns > 1) {
|
|
for (let i = 0; i < loop_turns; i++) {
|
|
sctx.beginPath();
|
|
sctx.arc(start_x + i * cond_spacing, top_y, cond_radius, Math.PI, 0);
|
|
sctx.arc(start_x + cond_spacing * 0.5 + i * cond_spacing, bot_y, cond_radius, 0, Math.PI);
|
|
//sctx.lineTo(start_x + i * cond_spacing + cond_radius, top_y);
|
|
sctx.fill();
|
|
|
|
sctx.beginPath();
|
|
sctx.moveTo(start_x + cond_spacing * 0.5 + i * cond_spacing + cond_radius, bot_y);
|
|
sctx.lineTo(start_x + (i+1) * cond_spacing + cond_radius, top_y);
|
|
sctx.arc(start_x + (i+1) * cond_spacing, top_y, cond_radius, 0, Math.PI, true);
|
|
sctx.lineTo(start_x + cond_spacing * 0.5 + i * cond_spacing - cond_radius, bot_y);
|
|
sctx.stroke();
|
|
}
|
|
} else {
|
|
sctx.beginPath();
|
|
sctx.arc(win_width/2, bot_y, cond_radius, 0, Math.PI);
|
|
sctx.arc(win_width/2, top_y, cond_radius, Math.PI, 0);
|
|
sctx.lineTo(win_width/2 + cond_radius, bot_y);
|
|
sctx.fill();
|
|
}
|
|
} else {
|
|
// "parallel" - means draw a two-arm parallel magloop:
|
|
sctx.beginPath();
|
|
sctx.arc(start_x + 0.5 * cond_spacing, bot_y, cond_radius, 0, Math.PI);
|
|
sctx.arc(start_x + 0.5 * cond_spacing, top_y, cond_radius, Math.PI, 0);
|
|
sctx.lineTo(start_x + 0.5 * cond_spacing + cond_radius, bot_y);
|
|
sctx.fill();
|
|
|
|
sctx.beginPath();
|
|
sctx.arc(start_x + 1.5 * cond_spacing, bot_y, cond_radius, 0, Math.PI);
|
|
sctx.arc(start_x + 1.5 * cond_spacing, top_y, cond_radius, Math.PI, 0);
|
|
sctx.lineTo(start_x + 1.5 * cond_spacing + cond_radius, bot_y);
|
|
sctx.fill();
|
|
}
|
|
|
|
// Draw left spacing arrow:
|
|
const dim_y = win_height * 0.8;
|
|
if(loop_turns > 1) {
|
|
sctx.beginPath();
|
|
//sctx.moveTo(0.5 * win_width - 0.5 * cond_spacing + shift - 20, dim_y);
|
|
sctx.moveTo(start_x + 0.5 * cond_spacing - 20, dim_y);
|
|
sctx.lineTo(start_x + 0.5 * cond_spacing , dim_y);
|
|
sctx.lineTo(start_x + 0.5 * cond_spacing - 7, dim_y + 7)
|
|
sctx.lineTo(start_x + 0.5 * cond_spacing - 7, dim_y - 7)
|
|
sctx.lineTo(start_x + 0.5 * cond_spacing , dim_y);
|
|
sctx.moveTo(start_x + 0.5 * cond_spacing , dim_y - 7);
|
|
sctx.lineTo(start_x + 0.5 * cond_spacing , dim_y + 7);
|
|
sctx.stroke();
|
|
// Draw right spacing arrow:
|
|
sctx.beginPath();
|
|
sctx.moveTo(start_x + 1.5 * cond_spacing + 20, dim_y);
|
|
sctx.lineTo(start_x + 1.5 * cond_spacing , dim_y);
|
|
sctx.lineTo(start_x + 1.5 * cond_spacing + 7, dim_y + 7)
|
|
sctx.lineTo(start_x + 1.5 * cond_spacing + 7, dim_y - 7)
|
|
sctx.lineTo(start_x + 1.5 * cond_spacing , dim_y);
|
|
sctx.moveTo(start_x + 1.5 * cond_spacing , dim_y - 7);
|
|
sctx.lineTo(start_x + 1.5 * cond_spacing , dim_y + 7);
|
|
sctx.stroke();
|
|
}
|
|
|
|
sctx.textAlign = "left";
|
|
sctx.font = turns_font;
|
|
sctx.fillText("N = " + loop_turns.toString() + ((loop_mode == "series") ? "" : " (P)"), 8, win_height * 0.1 + 3);
|
|
sctx.font = spacing_font;
|
|
sctx.fillText("c/a = ", 8, win_height * 0.1 + 18);
|
|
sctx.fillText((loop_spacing_slider.value*1.0).toPrecision(3).toString(), 8, win_height * 0.1 + 33);
|
|
sctx.font = normal_font;
|
|
|
|
// Multi-turn loop, so calculate C and SRF:
|
|
const L = loop_capacitance * 1e+12;
|
|
sctx.textAlign = "right";
|
|
sctx.fillText("C = " + L.toFixed(0).toString() + " pF", win_width-8, 18);
|
|
sctx.fillText("SRF = ", win_width-8, win_height * 0.1 + 18);
|
|
sctx.fillText((srf*1e-6).toPrecision(3).toString() + " MHz", win_width-8, win_height * 0.1 + 33);
|
|
|
|
sctx.textAlign = "right";
|
|
sctx.fillText("cond = " , win_width-8, dim_y + 10);
|
|
if(units == "metric") {
|
|
sctx.fillText(conductor_length.toPrecision(4).toString() + " m", win_width-8, dim_y + 24);
|
|
} else {
|
|
sctx.fillText((conductor_length * 3.28084).toPrecision(4).toString() + " ft", win_width-8, dim_y + 24);
|
|
}
|
|
|
|
// Draw spacing text:
|
|
if(loop_turns > 1) {
|
|
sctx.textAlign = "center";
|
|
const spc = (loop_turns > 1) ? loop_spacing_slider.value * conductor_diameter_slider.value : 0.0;
|
|
if(units == "metric") {
|
|
sctx.fillText("c = " + spc.toPrecision(3).toString() + " mm", start_x + cond_spacing, dim_y + 20);
|
|
} else {
|
|
sctx.fillText("c = " + (spc/25.4).toPrecision(3).toString() + " in", start_x + cond_spacing, dim_y + 20);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the global variables, which are all determined by physical dimensions, and are thus frequency-independent:
|
|
setGlobals();
|
|
// Update the frequencies, now that we have the sliders available:
|
|
updateFrequencies();
|
|
|
|
drawFrontDesign();
|
|
drawSideDesign();
|
|
|
|
const chartCanvas = document.getElementById("chartCanvas");
|
|
const chartCanvasContext = chartCanvas.getContext('2d');
|
|
|
|
function getMetricPrefix(num) {
|
|
if(num >= 1e9) return {val: num*1e-9, pfx:'G'};
|
|
if(num >= 1e6) return {val: num*1e-6, pfx:'M'};
|
|
if(num >= 1e3) return {val: num*1e-3, pfx:'k'};
|
|
if(num < 1e-12) return {val: num*1e15, pfx:'f'};
|
|
if(num < 1e-9) return {val: num*1e12, pfx:'p'};
|
|
if(num < 1e-6) return {val: num*1e9, pfx:'n'};
|
|
if(num < 1e-3) return {val: num*1e6, pfx:'\u03bc'};
|
|
if(num < 1.0) return {val: num*1e3, pfx:'m'};
|
|
return {val:num, pfx:' '};
|
|
}
|
|
|
|
function justifyText(pre, post) {
|
|
var whitespace = 22 - pre.length - post.length;
|
|
return pre + ' '.repeat(whitespace) + post;
|
|
}
|
|
|
|
var myChart = new Chart(chartCanvasContext, {
|
|
type: 'line',
|
|
data: {
|
|
datasets: [
|
|
{
|
|
label: 'Tuning Cap (pF)',
|
|
fill: false,
|
|
borderColor: 'green',
|
|
backgroundColor: 'green',
|
|
data: calculateTuningCapacitor(),
|
|
borderWidth: 1,
|
|
yAxisID: 'pfID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'Vcap (kV)',
|
|
fill: false,
|
|
borderColor: 'rgb(150, 150, 0)',
|
|
backgroundColor: 'rgb(200, 200, 0)',
|
|
data: calculateCapacitorVoltage(),
|
|
borderWidth: 1,
|
|
yAxisID: 'vID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'BW (kHz)',
|
|
fill: false,
|
|
borderColor: 'brown',
|
|
backgroundColor: 'brown',
|
|
data: calculateBandwidth(),
|
|
borderWidth: 1,
|
|
yAxisID: 'bwID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'Efficiency (%)',
|
|
fill: false,
|
|
borderColor: 'black',
|
|
backgroundColor: 'black',
|
|
data: calculateEfficiencyFactor(),
|
|
borderWidth: 1,
|
|
yAxisID: 'effID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'R-radiation (\u03A9)',
|
|
fill: false,
|
|
borderColor: 'red',
|
|
backgroundColor: 'red',
|
|
data: calculateRadiationResistance(),
|
|
borderWidth: 1,
|
|
yAxisID: 'mohmsID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'R-loop (\u03A9)',
|
|
fill: false,
|
|
borderColor: 'orange',
|
|
backgroundColor: 'orange',
|
|
data: calculateLossResistance(),
|
|
borderWidth: 1,
|
|
yAxisID: 'mohmsID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'Reactance (j\u03A9)',
|
|
fill: false,
|
|
borderColor: 'blue',
|
|
backgroundColor: 'blue',
|
|
data: calculateInductiveReactance(),
|
|
borderWidth: 1,
|
|
yAxisID: 'ohmsID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'Q',
|
|
fill: false,
|
|
borderColor: 'purple',
|
|
backgroundColor: 'purple',
|
|
data: calculateQualityFactor(),
|
|
borderWidth: 1,
|
|
yAxisID: 'qID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'I\u2092 (A)',
|
|
fill: false,
|
|
borderColor: 'rgb(0,255,255)',
|
|
backgroundColor: 'rgb(0,128,128)',
|
|
data: calculateCirculatingCurrent(),
|
|
borderWidth: 1,
|
|
yAxisID: 'ccID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'Perimeter (\u03BB)',
|
|
fill: false,
|
|
borderColor: 'rgb(130,130,130)',
|
|
backgroundColor: 'rgb(130,130,130)',
|
|
data: calculateAntennaSize(),
|
|
borderWidth: 1,
|
|
yAxisID: 'sizeID',
|
|
tension: 0.3,
|
|
},
|
|
{
|
|
label: 'Skin depth (\u03BCm)',
|
|
fill: false,
|
|
borderColor: 'rgb(75,75,75)',
|
|
backgroundColor: 'rgb(75,75,75)',
|
|
data: calculateSkinDepth(),
|
|
borderWidth: 1,
|
|
yAxisID: 'skinID',
|
|
tension: 0.3,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
type: 'linear',
|
|
position: 'bottom',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'Frequency (MHz)',
|
|
color: 'black',
|
|
font: {
|
|
weight: 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
autoSkip: false,
|
|
}
|
|
},
|
|
'mohmsID' : {
|
|
type: 'logarithmic',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: '\u03A9',
|
|
color: 'red',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
min: 0.001,
|
|
max: 1.0,
|
|
position: 'left',
|
|
},
|
|
'effID' : {
|
|
type: 'linear',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'Efficiency %',
|
|
color: 'black',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
},
|
|
min: 0.0,
|
|
max: 100.0,
|
|
position: 'left',
|
|
},
|
|
'bwID' : {
|
|
type: 'linear',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'BW kHz',
|
|
color: 'brown',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
max: 50.0,
|
|
min: 0.0,
|
|
position: 'left',
|
|
},
|
|
'vID' : {
|
|
type: 'logarithmic',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'kV',
|
|
color: 'rgb(150, 150, 0)',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
max: 50.0,
|
|
min: 0.05,
|
|
position: 'left',
|
|
},
|
|
'pfID' : {
|
|
type: 'logarithmic',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'pF',
|
|
color: 'green',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
max: 5000.0,
|
|
min: 5.0,
|
|
ticks: {
|
|
},
|
|
position: 'left',
|
|
},
|
|
'ohmsID' : {
|
|
type: 'linear',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'j\u03A9',
|
|
color: 'blue',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
min: 0.0,
|
|
max: 500.0,
|
|
position: 'right',
|
|
},
|
|
'qID' : {
|
|
type: 'linear',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'Q',
|
|
color: 'purple',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
min: 0.0,
|
|
max: 5000.0,
|
|
position: 'right',
|
|
},
|
|
'ccID' : {
|
|
type: 'logarithmic',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: 'A',
|
|
color: 'rgb(0,128,128)',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
min: 1.0,
|
|
max: 500.0,
|
|
position: 'right',
|
|
},
|
|
'sizeID' : {
|
|
type: 'linear',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: '\u03BB',
|
|
color: 'rgb(90,90,90)',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
},
|
|
max: 0.3,
|
|
min: 0.0,
|
|
position: 'right',
|
|
},
|
|
'skinID' : {
|
|
type: 'linear',
|
|
display: 'auto',
|
|
title: {
|
|
display: true,
|
|
text: '\u03B4 \u03BCm',
|
|
color: 'rgb(90,90,90)',
|
|
font: {
|
|
weight : 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
beginAtZero: true,
|
|
//max: 0.3,
|
|
},
|
|
min: 0.0,
|
|
position: 'right',
|
|
}
|
|
},
|
|
plugins: {
|
|
//showLines: true,
|
|
mode: 'nearest',
|
|
tooltip: {
|
|
enabled: true,
|
|
mode: 'index',
|
|
intersect: false,
|
|
position: 'nearest',
|
|
bodyFont: {
|
|
family: 'monospace',
|
|
},
|
|
callbacks: {
|
|
title: function(context) {
|
|
var value = context[0].parsed.x;
|
|
var lut = {0.1357:'2200', 0.1378:'2200', 0.472:'600', 0.479:'600', 1.8:'160', 1.875:'160', 3.5:'80', 3.8:'80', 5.3:'60', 5.4:'60', 7.0:'40', 7.3:'40', 10.1:'30', 10.15:'30', 14.0:'20', 14.35:'20', 18.068:'17', 18.168:'17', 21.0:'15', 21.45:'15',
|
|
24.89:'12', 24.99:'12', 26.965:'11', 27.855:'11', 28.0:'10', 29.7:'10', 35.0:'', 40.0:'', 45.0:'', 50.0:'6', 52.0:'6', 54.0:'6', 69.9:'4', 70.5:'4', 144.0:'2', 146.0:'2', 148.0:'2', 420.0:'0.7', 430.0:'0.7', 440.0:'0.7', 450.0:'0.7'};
|
|
var label = '' + value.toPrecision(4).toString() + ' MHz';
|
|
if(lut[value]) {
|
|
label += ' (';
|
|
label += lut[value] + ' m)';
|
|
}
|
|
return label;
|
|
},
|
|
label: function(context) {
|
|
var value = context.element.parsed.y;
|
|
var label = context.dataset.label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
if(context.dataset.label == "Tuning Cap (pF)") {
|
|
var num = getMetricPrefix(value * 1e-12);
|
|
label = justifyText("Tuning Cap: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + 'F');
|
|
} else
|
|
if(label[0] == "Q"){
|
|
label = justifyText("Q: ", Math.round(value).toString() + " ");
|
|
} else
|
|
if(label[0] == 'V'){
|
|
var num = getMetricPrefix(value * 1e3);
|
|
label = justifyText("Vcap: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + 'V');
|
|
} else
|
|
if(label[0] == 'B'){
|
|
var num = getMetricPrefix(value * 1e3);
|
|
label = justifyText("Bandwidth: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + 'Hz');
|
|
} else
|
|
if(label[0] == 'E'){
|
|
label = justifyText("Efficiency: ", value.toFixed(2).toString() + ' ' + ' %');
|
|
} else
|
|
if((label[0] == 'R') && (label[2] == 'r')){
|
|
var num = getMetricPrefix(value);
|
|
label = justifyText("R-rad: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + '\u03A9');
|
|
} else
|
|
if((label[0] == 'R') && (label[2] == 'l')){
|
|
var num = getMetricPrefix(value);
|
|
label = justifyText("R-loop: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + '\u03A9');
|
|
} else
|
|
if((label[0] == 'R') && (label[1] == 'e')){
|
|
var num = getMetricPrefix(value);
|
|
label = justifyText("Reactance: ", 'j' + num.val.toPrecision(3) + ' ' + num.pfx + '\u03A9');
|
|
} else
|
|
if(label[0] == 'I'){
|
|
var num = getMetricPrefix(value);
|
|
label = justifyText("I\u2092: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + 'A');
|
|
} else
|
|
if(label[0] == 'P'){
|
|
label = justifyText("Perimeter: ", value.toFixed(3).toString() + ' ' + '\u03BB');
|
|
} else
|
|
if(label[0] == 'S'){
|
|
var num = getMetricPrefix(value * 1e-6);
|
|
label = justifyText("Skin depth: ", num.val.toPrecision(3).toString() + ' ' + num.pfx + 'm');
|
|
} else {
|
|
label += value.toFixed(3).toString();
|
|
}
|
|
return label;
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|