Now with unit conversions

This commit is contained in:
Daniel Dunn
2025-04-03 05:32:47 -06:00
parent 6b27595a89
commit 70480d0920
4 changed files with 88 additions and 15 deletions

View File

@@ -1,16 +1,29 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Grid, TextField } from '@mui/material'; import { Grid, TextField, Select, MenuItem } from '@mui/material';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc'; import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import Qty from 'js-quantities'; import Qty from 'js-quantities';
import { parse } from 'path'; //
import { b } from 'vitest/dist/suite-IbNSsUWN.js';
const siPrefixes: { [key: string]: number } = {
'': 1,
k: 1000,
M: 1000000,
G: 1000000000,
T: 1000000000000,
m: 0.001,
u: 0.000001,
n: 0.000000001,
p: 0.000000000001
};
export default function NumericInputWithUnit(props: { export default function NumericInputWithUnit(props: {
value: { value: number; unit: string }; value: { value: number; unit: string };
onOwnChange: (value: { value: number; unit: string }, ...baseProps) => void; onOwnChange: (value: { value: number; unit: string }, ...baseProps) => void;
defaultPrefix?: string;
}) { }) {
const [inputValue, setInputValue] = useState(props.value.value); const [inputValue, setInputValue] = useState(props.value.value);
const [prefix, setPrefix] = useState(props.defaultPrefix || '');
const [unit, setUnit] = useState(props.value.unit); const [unit, setUnit] = useState(props.value.unit);
const [unitOptions, setUnitOptions] = useState<string[]>([]); const [unitOptions, setUnitOptions] = useState<string[]>([]);
@@ -34,7 +47,7 @@ export default function NumericInputWithUnit(props: {
setInputValue(newValue); setInputValue(newValue);
if (props.onOwnChange) { if (props.onOwnChange) {
try { try {
const qty = Qty(newValue, unit).to(val.unit); const qty = Qty(newValue * siPrefixes[prefix], unit).to(val.unit);
props.onOwnChange({ unit: val.unit, value: qty.scalar }); props.onOwnChange({ unit: val.unit, value: qty.scalar });
} catch (error) { } catch (error) {
console.error('Conversion error', error); console.error('Conversion error', error);
@@ -42,11 +55,31 @@ export default function NumericInputWithUnit(props: {
} }
}; };
const handlePrefixChange = (newPrefix: string) => {
const oldPrefixValue = siPrefixes[prefix];
const newPrefixValue = siPrefixes[newPrefix];
setPrefix(newPrefix);
// Value does not change, it is just re-formatted for display
// handleValueChange({
// value: (inputValue * oldPrefixValue) / newPrefixValue,
// unit: unit
// });
};
const handleUnitChange = (newUnit: string) => { const handleUnitChange = (newUnit: string) => {
if (!newUnit) return; if (!newUnit) return;
const oldInputValue = inputValue;
const oldUnit = unit;
setUnit(newUnit); setUnit(newUnit);
setPrefix('');
try { try {
const convertedValue = Qty(inputValue, unit).to(newUnit).scalar; const convertedValue = Qty(
oldInputValue * siPrefixes[prefix],
oldUnit
).to(newUnit).scalar;
setInputValue(convertedValue); setInputValue(convertedValue);
} catch (error) { } catch (error) {
console.error('Unit conversion error', error); console.error('Unit conversion error', error);
@@ -63,20 +96,46 @@ export default function NumericInputWithUnit(props: {
}; };
return ( return (
<Grid container spacing={2} alignItems="center"> <Grid
container
spacing={2}
alignItems="center"
style={{ minWidth: '20rem' }}
>
<Grid item xs={6}> <Grid item xs={6}>
<TextFieldWithDesc <TextFieldWithDesc
{...props.baseProps} {...props.baseProps}
type="number" type="number"
fullWidth fullWidth
value={inputValue} value={(inputValue / siPrefixes[prefix])
.toFixed(9)
.replace(/(\d*\.\d+?)0+$/, '$1')}
onOwnChange={(value) => onOwnChange={(value) =>
handleValueChange({ value: parseFloat(value), unit: unit }) handleValueChange({ value: parseFloat(value), unit: unit })
} }
label="Value" label="Value"
/> />
</Grid> </Grid>
<Grid item xs={6}>
<Grid item xs={3}>
<Select
fullWidth
label="Prefix"
title="Prefix"
value={prefix}
onChange={(event, newValue) => {
handlePrefixChange(newValue.props.value || '');
}}
>
{Object.keys(siPrefixes).map((key) => (
<MenuItem key={key} value={key}>
{key}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={3}>
<Autocomplete <Autocomplete
options={unitOptions} options={unitOptions}
value={unit} value={unit}

View File

@@ -7,6 +7,8 @@ export interface GenericCalcType {
title: string; title: string;
formula: string; formula: string;
unit: string; unit: string;
// Si prefix default
defaultPrefix?: string;
}[]; }[];
selections?: { selections?: {
title: string; title: string;
@@ -20,7 +22,7 @@ export interface GenericCalcType {
name: string; name: string;
title: string; title: string;
unit: string; unit: string;
defaultPrefix?: string;
// If absence, assume it's the default target var // If absence, assume it's the default target var
default?: number; default?: number;
}[]; }[];

View File

@@ -59,7 +59,8 @@ const voltagedropinwire: GenericCalcType = {
name: 'p', name: 'p',
title: 'Resistivity', title: 'Resistivity',
unit: 'Ω/m3', unit: 'Ω/m3',
default: 1 default: 1,
defaultPrefix: 'n'
}, },
{ {
name: 'x', name: 'x',

View File

@@ -13,7 +13,6 @@ import React, { useState } from 'react';
import ToolContent from '@components/ToolContent'; import ToolContent from '@components/ToolContent';
import { ToolComponentProps } from '@tools/defineTool'; import { ToolComponentProps } from '@tools/defineTool';
import ToolTextResult from '@components/result/ToolTextResult'; import ToolTextResult from '@components/result/ToolTextResult';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import NumericInputWithUnit from '@components/input/NumericInputWithUnit'; import NumericInputWithUnit from '@components/input/NumericInputWithUnit';
import { UpdateField } from '@components/options/ToolOptions'; import { UpdateField } from '@components/options/ToolOptions';
import { InitialValuesType } from './types'; import { InitialValuesType } from './types';
@@ -51,7 +50,7 @@ export default async function makeTool(
}>({}); }>({});
const [extraOutputs, setExtraOutputs] = useState<{ const [extraOutputs, setExtraOutputs] = useState<{
[key: string]: string; [key: string]: number;
}>({}); }>({});
const updateVarField = ( const updateVarField = (
@@ -240,8 +239,8 @@ export default async function makeTool(
<TableCell> <TableCell>
<NumericInputWithUnit <NumericInputWithUnit
title={variable.title} title={variable.title}
sx={{ width: '25ch' }}
description={valsBoundToPreset[variable.name] || ''} description={valsBoundToPreset[variable.name] || ''}
defaultPrefix={variable.defaultPrefix}
value={{ value={{
value: value:
values.outputVariable === variable.name values.outputVariable === variable.name
@@ -287,7 +286,17 @@ export default async function makeTool(
{calcData.extraOutputs?.map((extraOutput) => ( {calcData.extraOutputs?.map((extraOutput) => (
<TableRow key={extraOutput.title}> <TableRow key={extraOutput.title}>
<TableCell>{extraOutput.title}</TableCell> <TableCell>{extraOutput.title}</TableCell>
<TableCell>{extraOutputs[extraOutput.title]}</TableCell> <TableCell>
<NumericInputWithUnit
title={extraOutput.title}
disabled={true}
defaultPrefix={extraOutput.defaultPrefix}
value={{
value: extraOutputs[extraOutput.title],
unit: extraOutput.unit
}}
></NumericInputWithUnit>
</TableCell>
<TableCell>{extraOutput.unit}</TableCell> <TableCell>{extraOutput.unit}</TableCell>
<TableCell></TableCell> <TableCell></TableCell>
</TableRow> </TableRow>
@@ -345,7 +354,9 @@ export default async function makeTool(
const result: nerdamer.Expression = expr.evaluate(); const result: nerdamer.Expression = expr.evaluate();
if (result) { if (result) {
extraOutputs[extraOutput.title] = result.toDecimal(); extraOutputs[extraOutput.title] = parseFloat(
result.toDecimal()
);
} }
} }
} }