Created Dipole Antenna calculator.

This commit is contained in:
Roland 2024-06-20 09:51:29 -06:00
parent 8cf07c2ffb
commit 9986ce8a1f
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
5 changed files with 395 additions and 221 deletions

View file

@ -1,227 +1,10 @@
import React from "react";
import {BackButton, roundDigits, useTitle} from "./Common";
import {BackButton, useTitle} from "./Common";
import {Button, Header, Icon} from "./Theme";
import {GridColumn, GridRow, Input} from "semantic-ui-react";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import {useCalcQuery} from "../hooks/customHooks";
import {ColoredInput} from "./Apps";
function TemperatureCalculator() {
const [state, setState] = React.useState({
celsius: '',
fahrenheit: '',
kelvin: '',
})
const handleInputChange = (e, {name, value}) => {
if (value === '') {
// User has cleared the input;
setState({volt: '', ohm: '', amp: ''});
return
}
const numValue = parseFloat(value.endsWith('.') ? `${value}0` : value);
console.debug(`Calculating ${value} ${name}`);
let newCelsius;
let newKelvin;
let newFahrenheit;
if (name === 'fahrenheit') {
newFahrenheit = value;
newCelsius = roundDigits((numValue - 32) / 1.8);
newKelvin = roundDigits(newCelsius + 273.15);
} else if (name === 'celsius') {
newCelsius = value;
newFahrenheit = roundDigits((numValue * 1.8) + 32);
newKelvin = roundDigits(numValue + 273.15);
} else if (name === 'kelvin') {
newKelvin = value;
newCelsius = roundDigits(numValue - 273.15);
newFahrenheit = roundDigits((newCelsius * 1.8) + 32);
}
setState({
celsius: newCelsius,
fahrenheit: newFahrenheit,
kelvin: newKelvin
});
}
const handleClick = (e) => {
e.target.select();
}
const commonInputProps = {
fluid: true,
labelPosition: 'right',
type: 'number',
onChange: handleInputChange,
onClick: handleClick
};
return <Grid columns={3}>
<GridRow>
<GridColumn>
<Input value={state.celsius} label='°C' name='celsius' {...commonInputProps}/>
</GridColumn>
<GridColumn>
<Input value={state.fahrenheit} label='°F' name='fahrenheit' {...commonInputProps}/>
</GridColumn>
<GridColumn>
<Input value={state.kelvin} label='K' name='kelvin' {...commonInputProps}/>
</GridColumn>
</GridRow>
</Grid>
}
const initialState = {
volts: '',
amps: '',
ohms: '',
watts: '',
lastUpdated: [],
};
// Ohms Law functions. These take two values, and return the other two electrical values.
const voltsAmps = (volts, amps) => {
const watts = volts * amps;
const ohms = volts / amps;
return [roundDigits(watts) || 0, roundDigits(ohms) || 0]
};
const voltsOhms = (volts, ohms) => {
const amps = volts / ohms;
const watts = volts * amps;
return [roundDigits(amps) || 0, roundDigits(watts) || 0]
};
const ampsOhms = (amps, ohms) => {
const volts = amps * ohms;
const watts = volts * amps;
return [roundDigits(volts) || 0, roundDigits(watts) || 0]
};
const voltsWatts = (volts, watts) => {
const amps = watts / volts;
const ohms = volts / amps;
return [roundDigits(amps) || 0, roundDigits(ohms) || 0]
};
const ampsWatts = (amps, watts) => {
const volts = watts / amps;
const ohms = volts / amps;
return [roundDigits(volts) || 0, roundDigits(ohms) || 0]
};
const wattsOhms = (watts, ohms) => {
const volts = Math.sqrt(watts * ohms);
const amps = volts / ohms;
return [roundDigits(volts), roundDigits(amps)]
};
function ohmsLawReducer(state, action) {
const {type, value} = action;
let {volts, amps, ohms, watts, lastUpdated} = state;
// Only replace last updated when a different input was updated.
let newLastUpdated = lastUpdated;
if (lastUpdated.length === 0) {
newLastUpdated = [type];
} else if (lastUpdated.length === 1 && lastUpdated[0] !== type) {
newLastUpdated = [...lastUpdated, type];
} else if (lastUpdated.length > 1 && lastUpdated[1] !== type) {
newLastUpdated = [lastUpdated[1], type];
}
// Copy new value into it's respective state. Do not allow negative numbers.
if (type === 'amps') {
amps = Math.abs(value);
} else if (type === 'volts') {
volts = Math.abs(value);
} else if (type === 'ohms') {
ohms = Math.abs(value);
} else if (type === 'watts') {
watts = Math.abs(value);
}
// Sort the updated values, so we don't have to define everything twice.
const sortedLastUpdated = [...newLastUpdated].sort().join(',');
switch (sortedLastUpdated) {
case 'amps,ohms':
[volts, watts] = ampsOhms(amps, ohms);
break;
case 'amps,volts':
[watts, ohms] = voltsAmps(volts, amps);
break;
case 'amps,watts':
[volts, ohms] = ampsWatts(amps, watts);
break;
case 'ohms,volts':
[amps, watts] = voltsOhms(volts, ohms);
break;
case 'ohms,watts':
[volts, amps] = wattsOhms(watts, ohms);
break;
case 'volts,watts':
[amps, ohms] = voltsWatts(volts, watts);
break;
}
return {volts, amps, ohms, watts, lastUpdated: newLastUpdated};
}
function ElectricalCalculator() {
// Provides a form which provides 4 inputs (volts, amps, ohms, watts), and calculates the missing values.
const [state, dispatch] = React.useReducer(ohmsLawReducer, initialState);
const calculateColor = (name) => {
// Recently updated inputs are white, others are grey.
return state.lastUpdated.indexOf(name) >= 0 ? null : 'grey';
}
const inputProps = {
fluid: true,
type: 'number',
labelPosition: 'right',
onSelect: e => e.target.select(), // Select the contents of the Input when a user selects it.
};
return <div>
<Header as='h4'>Ohm's Law</Header>
<Grid columns={2}>
<GridRow>
<GridColumn>
<ColoredInput {...inputProps}
value={state.volts}
onChange={e => dispatch({type: 'volts', value: e.target.value})}
label='Volts'
color={calculateColor('volts')}
/>
</GridColumn>
<GridColumn>
<ColoredInput {...inputProps}
value={state.amps}
onChange={e => dispatch({type: 'amps', value: e.target.value})}
label='Amps'
color={calculateColor('amps')}
/>
</GridColumn>
</GridRow>
<GridRow>
<GridColumn>
<ColoredInput {...inputProps}
value={state.ohms}
onChange={e => dispatch({type: 'ohms', value: e.target.value})}
label='Ohms'
color={calculateColor('ohms')}
/>
</GridColumn>
<GridColumn>
<ColoredInput {...inputProps}
value={state.watts}
onChange={e => dispatch({type: 'watts', value: e.target.value})}
label='Watts'
color={calculateColor('watts')}
/>
</GridColumn>
</GridRow>
</Grid>
</div>
}
import {TemperatureCalculator} from "./calculators/TemperatureCalculator";
import {ElectricalCalculator} from "./calculators/ElectricalCalculators";
import {DipoleAntennaCalculator} from "./calculators/HamCalculators";
export function CalculatorsPage() {
const {calc, setCalc} = useCalcQuery();
@ -229,6 +12,7 @@ export function CalculatorsPage() {
const calculators = [
{key: 'temperature', icon: 'thermometer', button: 'Temperature', contents: <TemperatureCalculator/>},
{key: 'electrical', icon: 'lightning', button: 'Electrical', contents: <ElectricalCalculator/>},
{key: 'antenna', icon: 'signal', button: 'Antenna', contents: <DipoleAntennaCalculator/>},
];
const activeCalculator = calc ?

View file

@ -43,6 +43,11 @@ const SUGGESTED_APPS = [
title: 'Electrical Calculator',
description: 'Convert Volts, Ohms, Amps, Watts'
},
{
location: '/more/calculators?calc=antenna',
title: 'Antenna Calculator',
description: 'Calculate dipole length, wavelength.'
},
];
export const useSearch = (defaultLimit = 48, totalPages = 0, emptySearch = false, model) => {

View file

@ -0,0 +1,157 @@
import {roundDigits} from "../Common";
import React from "react";
import {Header} from "../Theme";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import {GridColumn, GridRow} from "semantic-ui-react";
import {ColoredInput} from "../Apps";
const initialState = {
volts: '',
amps: '',
ohms: '',
watts: '',
lastUpdated: [],
};
// Ohms Law functions. These take two values, and return the other two electrical values.
const voltsAmps = (volts, amps) => {
const watts = volts * amps;
const ohms = volts / amps;
return [roundDigits(watts) || 0, roundDigits(ohms) || 0]
};
const voltsOhms = (volts, ohms) => {
const amps = volts / ohms;
const watts = volts * amps;
return [roundDigits(amps) || 0, roundDigits(watts) || 0]
};
const ampsOhms = (amps, ohms) => {
const volts = amps * ohms;
const watts = volts * amps;
return [roundDigits(volts) || 0, roundDigits(watts) || 0]
};
const voltsWatts = (volts, watts) => {
const amps = watts / volts;
const ohms = volts / amps;
return [roundDigits(amps) || 0, roundDigits(ohms) || 0]
};
const ampsWatts = (amps, watts) => {
const volts = watts / amps;
const ohms = volts / amps;
return [roundDigits(volts) || 0, roundDigits(ohms) || 0]
};
const wattsOhms = (watts, ohms) => {
const volts = Math.sqrt(watts * ohms);
const amps = volts / ohms;
return [roundDigits(volts), roundDigits(amps)]
};
function ohmsLawReducer(state, action) {
const {type, value} = action;
let {volts, amps, ohms, watts, lastUpdated} = state;
// Only replace last updated when a different input was updated.
let newLastUpdated = lastUpdated;
if (lastUpdated.length === 0) {
newLastUpdated = [type];
} else if (lastUpdated.length === 1 && lastUpdated[0] !== type) {
newLastUpdated = [...lastUpdated, type];
} else if (lastUpdated.length > 1 && lastUpdated[1] !== type) {
newLastUpdated = [lastUpdated[1], type];
}
// Copy new value into it's respective state. Do not allow negative numbers.
if (type === 'amps') {
amps = Math.abs(value);
} else if (type === 'volts') {
volts = Math.abs(value);
} else if (type === 'ohms') {
ohms = Math.abs(value);
} else if (type === 'watts') {
watts = Math.abs(value);
}
// Sort the updated values, so we don't have to define everything twice.
const sortedLastUpdated = [...newLastUpdated].sort().join(',');
switch (sortedLastUpdated) {
case 'amps,ohms':
[volts, watts] = ampsOhms(amps, ohms);
break;
case 'amps,volts':
[watts, ohms] = voltsAmps(volts, amps);
break;
case 'amps,watts':
[volts, ohms] = ampsWatts(amps, watts);
break;
case 'ohms,volts':
[amps, watts] = voltsOhms(volts, ohms);
break;
case 'ohms,watts':
[volts, amps] = wattsOhms(watts, ohms);
break;
case 'volts,watts':
[amps, ohms] = voltsWatts(volts, watts);
break;
}
return {volts, amps, ohms, watts, lastUpdated: newLastUpdated};
}
export function ElectricalCalculator() {
// Provides a form which provides 4 inputs (volts, amps, ohms, watts), and calculates the missing values.
const [state, dispatch] = React.useReducer(ohmsLawReducer, initialState);
const calculateColor = (name) => {
// Recently updated inputs are white, others are grey.
return state.lastUpdated.indexOf(name) >= 0 ? null : 'grey';
}
const inputProps = {
fluid: true,
type: 'number',
labelPosition: 'right',
onSelect: e => e.target.select(), // Select the contents of the Input when a user selects it.
};
return <div>
<Header as='h4'>Ohm's Law</Header>
<Grid columns={2}>
<GridRow>
<GridColumn>
<ColoredInput {...inputProps}
value={state.volts}
onChange={e => dispatch({type: 'volts', value: e.target.value})}
label='Volts'
color={calculateColor('volts')}
/>
</GridColumn>
<GridColumn>
<ColoredInput {...inputProps}
value={state.amps}
onChange={e => dispatch({type: 'amps', value: e.target.value})}
label='Amps'
color={calculateColor('amps')}
/>
</GridColumn>
</GridRow>
<GridRow>
<GridColumn>
<ColoredInput {...inputProps}
value={state.ohms}
onChange={e => dispatch({type: 'ohms', value: e.target.value})}
label='Ohms'
color={calculateColor('ohms')}
/>
</GridColumn>
<GridColumn>
<ColoredInput {...inputProps}
value={state.watts}
onChange={e => dispatch({type: 'watts', value: e.target.value})}
label='Watts'
color={calculateColor('watts')}
/>
</GridColumn>
</GridRow>
</Grid>
</div>
}

View file

@ -0,0 +1,158 @@
import React from "react";
import {TableBody, TableCell, TableHeader, TableHeaderCell, TableRow} from "semantic-ui-react";
import {Header, Table} from "../Theme";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import {roundDigits} from "../Common";
import {ColoredInput} from "../Apps";
function convertFeetToFeetAndInches(decimalFeet) {
// Extract the integer part for feet
const feet = Math.floor(decimalFeet);
// Calculate the remainder in feet and convert it to inches (1 foot = 12 inches)
const inches = Math.round((decimalFeet - feet) * 12);
return [feet, inches];
}
const defaultMhzInputValue = '144.2';
export function DipoleAntennaCalculator() {
const [mhz, setMhz] = React.useState(144.2);
const [wave, setWave] = React.useState(2.08);
const [mhzInputValue, setMhzInputValue] = React.useState(defaultMhzInputValue);
const [waveInputValue, setWaveInputValue] = React.useState(0);
const [feet, setFeet] = React.useState(0);
const [inches, setInches] = React.useState(0);
const [meters, setMeters] = React.useState(0);
const [cm, setCM] = React.useState(0);
const c = 299792458;
// Reduce the full wavelength by the multiplier.
const lengths = [
{name: '½ Antenna Length', multiplier: 0.5},
{name: '¼ Antenna Length', multiplier: 0.25},
{name: '⅝ Antenna Length', multiplier: 0.625},
];
React.useEffect(() => {
// Calculate full wavelength.
const newFeet = 936 / mhz;
setFeet(newFeet);
setInches(newFeet * 12);
setMeters(newFeet * 0.3048);
setCM(newFeet * 0.3048 * 100);
setWaveInputValue(roundDigits(c / (mhz * 1e6)));
}, [mhz]);
React.useEffect(() => {
if (wave && parseFloat(wave) > 0) {
setMhzInputValue(roundDigits(c / parseFloat(wave) / 1e6));
}
}, [wave])
const mhzStorageKey = 'dipole_mhz';
const storageMhzValue = localStorage.getItem(mhzStorageKey);
React.useEffect(() => {
if (storageMhzValue) {
setMhz(parseFloat(storageMhzValue));
setMhzInputValue(storageMhzValue);
}
}, []);
const handleMhzChange = (e, {value}) => {
setMhzInputValue(value);
setMhz(value && parseFloat(value) > 0 ? parseFloat(value) : 0);
localStorage.setItem(mhzStorageKey, value);
}
const handleWaveChange = (e, {value}) => {
setWaveInputValue(value);
setWave(value && parseFloat(value) > 0 ? parseFloat(value) : 0);
}
const [mhzSelected, setMhzSelected] = React.useState(true);
const handleClick = (e, name) => {
if (name === 'mhz') {
setMhzSelected(true);
} else {
setMhzSelected(false);
}
}
return <>
<Header as='h2'>Dipole Antenna</Header>
<Grid columns={2}>
<Grid.Row>
<Grid.Column>
<ColoredInput fluid
label='MHz'
labelPosition='right'
value={mhzInputValue}
onChange={handleMhzChange}
onSelect={e => handleClick(e, 'mhz')}
color={mhzSelected ? null : 'grey'}
/>
</Grid.Column>
<Grid.Column>
<ColoredInput fluid
label='Wavelength'
labelPosition='right'
value={waveInputValue}
onChange={handleWaveChange}
onSelect={e => handleClick(e, 'wave')}
color={mhzSelected ? 'grey' : null}
/>
</Grid.Column>
</Grid.Row>
</Grid>
{lengths.map(i => {
const [feet_, inches_] = convertFeetToFeetAndInches(feet * i.multiplier);
const [half_feet, half_inches] = convertFeetToFeetAndInches(feet * i.multiplier / 2);
return <Table key={i.name} size='large' striped unstackable>
<TableHeader>
<TableRow>
<TableHeaderCell>{i.name}</TableHeaderCell>
<TableHeaderCell>Leg Length</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>
{feet_} ft. {inches_} in.
</TableCell>
<TableCell>
{half_feet} ft. {half_inches} in.
</TableCell>
</TableRow>
<TableRow>
<TableCell>
{roundDigits(inches * i.multiplier)} inches
</TableCell>
<TableCell>
{roundDigits(inches * i.multiplier / 2)} inches
</TableCell>
</TableRow>
<TableRow>
<TableCell>
{roundDigits(meters * i.multiplier)} meters
</TableCell>
<TableCell>
{roundDigits(meters * i.multiplier / 2)} meters
</TableCell>
</TableRow>
<TableRow>
<TableCell>
{roundDigits(cm * i.multiplier)} cm.
</TableCell>
<TableCell>
{roundDigits(cm * i.multiplier / 2)} cm.
</TableCell>
</TableRow>
</TableBody>
</Table>
})}
</>
}

View file

@ -0,0 +1,70 @@
import React from "react";
import {roundDigits} from "../Common";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import {GridColumn, GridRow, Input} from "semantic-ui-react";
export function TemperatureCalculator() {
const [state, setState] = React.useState({
celsius: '',
fahrenheit: '',
kelvin: '',
})
const handleInputChange = (e, {name, value}) => {
if (value === '') {
// User has cleared the input;
setState({volt: '', ohm: '', amp: ''});
return
}
const numValue = parseFloat(value.endsWith('.') ? `${value}0` : value);
console.debug(`Calculating ${value} ${name}`);
let newCelsius;
let newKelvin;
let newFahrenheit;
if (name === 'fahrenheit') {
newFahrenheit = value;
newCelsius = roundDigits((numValue - 32) / 1.8);
newKelvin = roundDigits(newCelsius + 273.15);
} else if (name === 'celsius') {
newCelsius = value;
newFahrenheit = roundDigits((numValue * 1.8) + 32);
newKelvin = roundDigits(numValue + 273.15);
} else if (name === 'kelvin') {
newKelvin = value;
newCelsius = roundDigits(numValue - 273.15);
newFahrenheit = roundDigits((newCelsius * 1.8) + 32);
}
setState({
celsius: newCelsius,
fahrenheit: newFahrenheit,
kelvin: newKelvin
});
}
const handleClick = (e) => {
e.target.select();
}
const commonInputProps = {
fluid: true,
labelPosition: 'right',
type: 'number',
onChange: handleInputChange,
onClick: handleClick
};
return <Grid columns={3}>
<GridRow>
<GridColumn>
<Input value={state.celsius} label='°C' name='celsius' {...commonInputProps}/>
</GridColumn>
<GridColumn>
<Input value={state.fahrenheit} label='°F' name='fahrenheit' {...commonInputProps}/>
</GridColumn>
<GridColumn>
<Input value={state.kelvin} label='K' name='kelvin' {...commonInputProps}/>
</GridColumn>
</GridRow>
</Grid>
}