diff --git a/components/calculators/Interactive/index.tsx b/components/calculators/Interactive/index.tsx index 15c1729f..05ebeead 100644 --- a/components/calculators/Interactive/index.tsx +++ b/components/calculators/Interactive/index.tsx @@ -18,7 +18,7 @@ const InteractiveCalculator: FunctionComponent = ({ i18n: { language }, } = useTranslation(); - const { fields = {}, result, latex } = Calculator(equation, value, language); + const { inputs = [], result, latex } = Calculator(equation, value, language); const handleChange = (event: FormEvent, key: string) => { onChangeCallback && @@ -31,12 +31,11 @@ const InteractiveCalculator: FunctionComponent = ({ return ( - {Object.keys(fields).map((key: string) => { - const { label, precision } = fields[key]; + {inputs.map(({ key, precision, placeholder }) => { return ( = ({ i18n: { language }, } = useTranslation(); - const { fields = {}, result, latex } = Calculator(equation, value, language); + const { inputs = [], result, latex } = Calculator(equation, value, language); const handleChange = (event: FormEvent, key: string) => { onChangeCallback && @@ -31,12 +31,11 @@ const InteractiveCalculator: FunctionComponent = ({ return ( - {Object.keys(fields).map((key: string) => { - const { label, precision } = fields[key]; + {inputs.map(({ key, precision, placeholder }) => { return ( = { + distanceMly: { + key: "distanceMly", + precision: 1, + sigFigs: 3, + placeholder: "d", + }, + m15: { key: "m15", precision: 2, placeholder: "\\Delta m_{15}" }, + peakApparentMagnitude: { + key: "peakApparentMagnitude", + precision: 1, + placeholder: "m", + }, + peakAbsoluteMagnitude: { + key: "peakAbsoluteMagnitude", + precision: 1, + placeholder: "M", + }, +}; + +const Config: Record = { + peakAbsoluteMagnitude: { + latex: ({ result, constants, variables }) => + `${result} = ${constants.A} + ${constants.B}\\left ( ${variables.m15} \\right )`, + constants: { + A: { value: -23.598, precision: 2 }, + B: { value: 6.457, precision: 2 }, + }, + inputs: [Variables.m15], + result: Variables.peakAbsoluteMagnitude, + }, + distanceMly: { + latex: ({ result, constants, variables }) => + `${result} = \\left (${constants.A}\\right )${constants.B}^{\\frac{${variables.peakApparentMagnitude} - ${variables.peakAbsoluteMagnitude}}{${constants.C}}} + ${constants.D}`, + constants: { + A: { value: 3.26, precision: 2 }, + B: { value: 10 }, + C: { value: 5 }, + D: { value: 1 }, + }, + inputs: [Variables.peakApparentMagnitude, Variables.peakAbsoluteMagnitude], + result: { + ...Variables.distanceMly, + addUnit: (value) => `${value}\\space\\text{Mly}`, + }, + }, +}; + +export default Config; diff --git a/components/calculators/lib/equations.ts b/components/calculators/lib/equations.ts index 14a81447..4b88545c 100644 --- a/components/calculators/lib/equations.ts +++ b/components/calculators/lib/equations.ts @@ -2,41 +2,36 @@ import round from "lodash/round"; import isNumber from "lodash/isNumber"; import { Equation, EquationComposer } from "@/types/calculators"; -const peakAbsoluteMagnitude: EquationComposer = ({ m15 }) => { - const A = -23.598; - const B = 6.457; - +const peakAbsoluteMagnitude: EquationComposer = ({ m15 }, { A, B }) => { if (isNumber(m15)) { - const result = round(A + B * m15, 1); + const result = round(A.value + B.value * m15, 1); - return { constants: { A, B }, result }; + return result; } - return { constants: { A, B } }; + return undefined; }; -const supernovaDistance: EquationComposer = ({ - peakApparentMagnitude, - peakAbsoluteMagnitude, -}) => { - const A = 3.26; - const B = 10; - const C = 5; - const D = 1; - +const distanceMly: EquationComposer = ( + { peakApparentMagnitude, peakAbsoluteMagnitude }, + { A, B, C, D } +) => { if (isNumber(peakApparentMagnitude) && isNumber(peakAbsoluteMagnitude)) { - const exponent = (peakApparentMagnitude - peakAbsoluteMagnitude) / C + D; - const result = round((A * Math.pow(B, exponent)) / Math.pow(B, 6)); + const exponent = + (peakApparentMagnitude - peakAbsoluteMagnitude) / C.value + D.value; + const result = round( + (A.value * Math.pow(B.value, exponent)) / Math.pow(10, 6) + ); - return { constants: { A, B, C, D }, result }; + return result; } - return { constants: { A, B, C, D } }; + return undefined; }; const Equations: Record = { peakAbsoluteMagnitude, - supernovaDistance, + distanceMly, }; export default Equations; diff --git a/components/calculators/lib/fields.ts b/components/calculators/lib/fields.ts deleted file mode 100644 index f1a4c11e..00000000 --- a/components/calculators/lib/fields.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CalculatorInput, Equation } from "@/types/calculators"; -import placeholders from "./placeholders"; - -const FormFields: Record> = { - peakAbsoluteMagnitude: { - m15: { key: "m15", precision: 2, label: `${placeholders.m15} =` }, - }, - supernovaDistance: { - peakApparentMagnitude: { - key: "peakApparentMagnitude", - precision: 1, - label: `${placeholders.peakApparentMagnitude} =`, - }, - peakAbsoluteMagnitude: { - key: "peakAbsoluteMagnitude", - precision: 1, - label: `${placeholders.peakAbsoluteMagnitude} =`, - }, - }, -}; - -export default FormFields; diff --git a/components/calculators/lib/index.ts b/components/calculators/lib/index.ts index 80278517..dc120e86 100644 --- a/components/calculators/lib/index.ts +++ b/components/calculators/lib/index.ts @@ -5,8 +5,8 @@ import { NonNullableCalculatorValues, } from "@/types/calculators"; import Equations from "./equations"; -import LaTeX from "./latex"; -import FormFields from "./fields"; +import LaTeXComposer from "./latex"; +import Config from "./config"; const cleanInput = (values: CalculatorValues): NonNullableCalculatorValues => { const nonNullValues: NonNullableCalculatorValues = {}; @@ -19,16 +19,24 @@ const cleanInput = (values: CalculatorValues): NonNullableCalculatorValues => { }; const Calculator: Calculator = (equation, value, locale = fallbackLng) => { - const cleaned = cleanInput(value); + const variables = cleanInput(value); + const config = Config[equation]; - const { result } = Equations[equation](cleaned); + if (typeof config === "undefined") { + console.error(`Equation ${equation} does not exist`); + } + + const { inputs, constants } = config; + + const result = Equations[equation](variables, constants); return { - fields: FormFields[equation], + inputs, result, - latex: LaTeX[equation]( + latex: LaTeXComposer( + config, { - ...cleaned, + variables, result, }, locale diff --git a/components/calculators/lib/latex.ts b/components/calculators/lib/latex.ts index 193fbd49..f09c548c 100644 --- a/components/calculators/lib/latex.ts +++ b/components/calculators/lib/latex.ts @@ -1,76 +1,91 @@ import isNumber from "lodash/isNumber"; import { fallbackLng } from "@/lib/i18n/settings"; -import { LaTeXComposer } from "@/types/calculators"; -import placeholders from "./placeholders"; -import Equations from "./equations"; +import { + Variable, + Constant, + Result, + EquationConfig, +} from "@/types/calculators"; +import { Variables } from "./config"; const withClass = (value: string) => `\\class{calc-output}{${value}}`; -const peakAbsoluteMagnitude: LaTeXComposer = ( - { result = placeholders.peakAbsoluteMagnitude, m15 = placeholders.m15 }, - locale = fallbackLng -) => { - const { - constants: { A, B }, - } = Equations.peakAbsoluteMagnitude({}); +const formatConstant = ({ precision, value }: Constant, locale = fallbackLng) => + Intl.NumberFormat(locale, { + minimumFractionDigits: precision, + maximumFractionDigits: precision, + }).format(value); - const { format } = new Intl.NumberFormat(locale, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); - const { format: resultFormat } = new Intl.NumberFormat(locale, { - minimumFractionDigits: 1, - maximumFractionDigits: 1, - }); +const formatVariable = ({ + variable, + config: { precision, sigFigs, placeholder }, + locale = fallbackLng, +}: { + variable?: number; + config: Variable; + locale?: string; +}) => { + if (isNumber(variable)) { + return Intl.NumberFormat(locale, { + minimumFractionDigits: precision, + maximumFractionDigits: precision, + maximumSignificantDigits: sigFigs, + }).format(variable); + } + + return placeholder; +}; - const displayResult = isNumber(result) ? resultFormat(result) : result; - const displayM15 = isNumber(m15) ? format(m15) : m15; +const formatResult = ({ + result: variable, + config: { addUnit, ...config }, + locale = fallbackLng, +}: { + result?: number; + config: Result; + locale?: string; +}) => { + if (isNumber(variable) && addUnit) { + return withClass(addUnit(formatVariable({ variable, config, locale }))); + } - return `${withClass(displayResult)} = ${format(A)} + ${format( - B - )}\\left ( ${displayM15} \\right )`; + return withClass( + formatVariable({ + variable, + config, + locale, + }) + ); }; -const supernovaDistance: LaTeXComposer = ( - { - result = placeholders.distance, - peakAbsoluteMagnitude = placeholders.peakAbsoluteMagnitude, - peakApparentMagnitude = placeholders.peakApparentMagnitude, - }, +const LaTeXComposer = ( + equation: EquationConfig, + values: { result?: number; variables: Record }, locale = fallbackLng ) => { - const { - constants: { A, B, C, D }, - } = Equations.supernovaDistance({}); + const { latex, constants, result: resultConfig } = equation; + const { result, variables } = values; - const { format } = new Intl.NumberFormat(locale, { - minimumFractionDigits: 1, - maximumFractionDigits: 1, - }); + const formattedConstants: Record = {}; + const formattedVariables: Record = {}; - const displayResult = isNumber(result) - ? `${Intl.NumberFormat(locale, { - maximumSignificantDigits: 3, - }).format(result)}\\space\\text{Mly}` - : result; - const displayPeakAbsolute = isNumber(peakAbsoluteMagnitude) - ? format(peakAbsoluteMagnitude) - : peakAbsoluteMagnitude; - const displayPeakApparent = isNumber(peakApparentMagnitude) - ? format(peakApparentMagnitude) - : peakApparentMagnitude; + Object.entries(constants).map(([key, constant]) => { + formattedConstants[key] = formatConstant(constant, locale); + }); - return `${withClass(displayResult)} = \\left (${Intl.NumberFormat(locale, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }).format( - A - )}\\right )${B}^{\\frac{${displayPeakApparent} - ${displayPeakAbsolute}}{${C}} + ${D}}`; -}; + Object.entries(variables).map(([key, variable]) => { + formattedVariables[key] = formatVariable({ + variable, + config: Variables[key], + locale, + }); + }); -const LaTeX = { - peakAbsoluteMagnitude, - supernovaDistance, + return latex({ + result: formatResult({ result, config: resultConfig, locale }), + constants: formattedConstants, + variables: formattedVariables, + }); }; -export default LaTeX; +export default LaTeXComposer; diff --git a/components/calculators/lib/placeholders.ts b/components/calculators/lib/placeholders.ts deleted file mode 100644 index 1c789c8d..00000000 --- a/components/calculators/lib/placeholders.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default { - distance: "d", - m15: "\\Delta m_{15}", - peakAbsoluteMagnitude: "M", - peakApparentMagnitude: "m", -}; diff --git a/public/localeStrings/en/translation.json b/public/localeStrings/en/translation.json index c6726312..9710ad57 100644 --- a/public/localeStrings/en/translation.json +++ b/public/localeStrings/en/translation.json @@ -308,7 +308,7 @@ }, "calculators": { "output": { - "supernovaDistance": "{{result}} Mega light-years", + "distanceMly": "{{result}} Mega light-years", "peakAbsoluteMagnitude": "Peak absolute magnitude {{result}}" } } diff --git a/types/calculators.d.ts b/types/calculators.d.ts index d9a39a27..66c432c5 100644 --- a/types/calculators.d.ts +++ b/types/calculators.d.ts @@ -1,11 +1,34 @@ -export type Equation = "peakAbsoluteMagnitude" | "supernovaDistance"; +export type Equation = "peakAbsoluteMagnitude" | "distanceMly"; export type CalculatorValues = Record; export type NonNullableCalculatorValues = Record; -export interface CalculatorInput { +export interface Variable { key: string; - label: string; precision: number; + sigFigs?: number; + placeholder: string; +} + +export interface Constant { + value: number; + precision?: number; +} + +export interface Result extends Variable { + addUnit?: (value: string | number) => string; +} + +export interface LaTeXBindings { + result: string | number; + constants: Record; + variables: Record; +} + +export interface EquationConfig { + latex: (props: LaTeXBindings) => string; + constants: Record; + inputs: Array; + result: Result; } export type Calculator = ( @@ -13,7 +36,7 @@ export type Calculator = ( variables: T, locale?: string ) => { - fields: Record; + inputs: Array; result: number | undefined; latex: string; }; @@ -26,12 +49,7 @@ export interface InteractiveCalculatorProps { value?: T; } -export type EquationComposer = (values: NonNullableCalculatorValues) => { - constants: Record; - result?: number; -}; - -export type LaTeXComposer = ( - variables: Record, - locale?: string -) => string; +export type EquationComposer = ( + values: NonNullableCalculatorValues, + constants: Record +) => number | undefined;