From dce9ae38594d3d419856e57dfff811b921fb49a7 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Wed, 27 Nov 2024 12:03:01 +0000 Subject: [PATCH] refactor(frontend): Results table - Change secondary parameters to take an interval, rather than an array index, in order to match how other table cell parameters are passed around. - Remove the unit selects from the `ResultsTable` component, so that units are set under Model > Secondary Parameters. - Only allow one selected time unit for all time intervals. --- .../model/secondary/TimeIntervalsTable.tsx | 30 ++++-- .../src/features/results/ResultsTab.tsx | 96 +------------------ .../src/features/results/ResultsTable.tsx | 22 +---- frontend-v2/src/features/results/columns.tsx | 4 +- .../src/features/results/useParameters.tsx | 81 +++++++++------- .../src/features/results/useTableRows.ts | 9 +- frontend-v2/src/features/results/utils.ts | 2 +- 7 files changed, 81 insertions(+), 163 deletions(-) diff --git a/frontend-v2/src/features/model/secondary/TimeIntervalsTable.tsx b/frontend-v2/src/features/model/secondary/TimeIntervalsTable.tsx index d9caadb1..f4311fd3 100644 --- a/frontend-v2/src/features/model/secondary/TimeIntervalsTable.tsx +++ b/frontend-v2/src/features/model/secondary/TimeIntervalsTable.tsx @@ -46,10 +46,12 @@ function useTimeUnits() { return hourUnit?.compatible_units || []; } -function TimeUnitSelect({ interval, onChange }: TimeUnitSelectProps) { +function TimeUnitSelect({ onChange }: TimeUnitSelectProps) { const defaultTimeUnit = 9; // set hours by default + const [intervals, setIntervals] = useModelTimeIntervals(); + const interval = intervals[0]; const [selectedUnit, setSelectedUnit] = useState( - interval.unit || defaultTimeUnit, + interval?.unit || defaultTimeUnit, ); const timeUnits = useTimeUnits(); const timeUnitOptions = @@ -59,7 +61,7 @@ function TimeUnitSelect({ interval, onChange }: TimeUnitSelectProps) { const unit = timeUnits?.find((unit) => unit.id === event.target.value); if (unit) { setSelectedUnit(+unit.id); - onChange({ ...interval, unit: +unit.id }); + setIntervals(intervals.map((i) => ({ ...i, unit: +unit.id }))); } } @@ -80,11 +82,20 @@ type IntervalRowProps = { interval: TimeIntervalRead; onDelete: () => void; onUpdate: (interval: TimeIntervalRead) => void; + editUnits: boolean; }; -function IntervalRow({ interval, onDelete, onUpdate }: IntervalRowProps) { +function IntervalRow({ + interval, + onDelete, + onUpdate, + editUnits = false, +}: IntervalRowProps) { const [start, setStart] = useState(interval.start_time); const [end, setEnd] = useState(interval.end_time); + const units = useTimeUnits(); + const intervalUnit = units?.find((unit) => +unit.id === interval.unit); + function onChangeStart(event: React.ChangeEvent) { const newStartTime = parseFloat(event.target.value); setStart(newStartTime); @@ -105,7 +116,11 @@ function IntervalRow({ interval, onDelete, onUpdate }: IntervalRowProps) { - + {editUnits ? ( + + ) : ( + intervalUnit?.symbol + )} @@ -153,6 +168,7 @@ const TimeIntervalsTable: FC = (props) => { return ( <> + Start time @@ -161,17 +177,17 @@ const TimeIntervalsTable: FC = (props) => { Remove - {intervals.map((interval) => ( + {intervals.map((interval, index) => ( ))}
- ); }; diff --git a/frontend-v2/src/features/results/ResultsTab.tsx b/frontend-v2/src/features/results/ResultsTab.tsx index 7dd6c66b..a3e0b31f 100644 --- a/frontend-v2/src/features/results/ResultsTab.tsx +++ b/frontend-v2/src/features/results/ResultsTab.tsx @@ -1,6 +1,5 @@ import { FC, useState } from "react"; import { - Box, FormControl, InputLabel, MenuItem, @@ -16,65 +15,6 @@ import { useConcentrationVariables } from "./useConcentrationVariables"; import { useParameters, Parameter } from "./useParameters"; import { ResultsTable } from "./ResultsTable"; import { useModelTimeIntervals } from "../../hooks/useModelTimeIntervals"; -import { useUnits } from "./useUnits"; - -function TimeUnitSelect({ - timeUnit, - onChangeTimeUnit, -}: { - timeUnit: string; - onChangeTimeUnit: (event: SelectChangeEvent) => void; -}) { - const units = useUnits(); - const timeUnits = units?.find( - (unit) => unit.symbol === "h", - )?.compatible_units; - return ( - - Time Unit - - - ); -} - -function ConcentrationUnitSelect({ - concentrationUnit, - onChangeConcentrationUnit, -}: { - concentrationUnit: string; - onChangeConcentrationUnit: (event: SelectChangeEvent) => void; -}) { - const units = useUnits(); - const concentrationUnits = units?.find( - (unit) => unit.symbol === "pmol/L", - )?.compatible_units; - return ( - - Concentration Unit - - - ); -} const options = [ { name: "Parameters", value: "parameters" }, @@ -103,22 +43,10 @@ type RowFilter = { }; const ResultsTab: FC = () => { - const [timeUnit, setTimeUnit] = useState("h"); - const [concentrationUnit, setConcentrationUnit] = useState("pmol/L"); - function onChangeTimeUnit(event: SelectChangeEvent) { - setTimeUnit(event.target.value); - } - function onChangeConcentrationUnit(event: SelectChangeEvent) { - setConcentrationUnit(event.target.value); - } - const { groups = [] } = useSubjectGroups(); const [intervals] = useModelTimeIntervals(); const concentrationVariables = useConcentrationVariables(); - const parameters = useParameters({ - variableUnit: concentrationUnit, - timeUnit, - }); + const parameters = useParameters(); const [columns, setColumns] = useState("parameters"); const [rows, setRows] = useState("variables"); @@ -360,27 +288,7 @@ const ResultsTab: FC = () => { intervalIndex={intervalIndex} rows={rowData} rowColumn={rowColumn} - concentrationUnit={concentrationUnit} - timeUnit={timeUnit} - > - - - - - + /> ); // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/frontend-v2/src/features/results/ResultsTable.tsx b/frontend-v2/src/features/results/ResultsTable.tsx index 5ddca275..c9ca68ea 100644 --- a/frontend-v2/src/features/results/ResultsTable.tsx +++ b/frontend-v2/src/features/results/ResultsTable.tsx @@ -6,11 +6,11 @@ import { TableHead, TableRow, } from "@mui/material"; -import { FC, ReactElement } from "react"; +import { FC } from "react"; import useSubjectGroups from "../../hooks/useSubjectGroups"; -import { useParameters } from "./useParameters"; +import { useParameterNames } from "./useParameters"; import { useConcentrationVariables } from "./useConcentrationVariables"; import { FilterIndex, RowData } from "./ResultsTab"; @@ -77,9 +77,6 @@ interface ResultsTableProps { parameterIndex: FilterIndex; rows: RowData; rowColumn: string; - concentrationUnit: string; - timeUnit: string; - children?: ReactElement; } /** @@ -92,8 +89,6 @@ interface ResultsTableProps { * @param parameterIndex "rows" or "columns" or the index of the parameter to display in the table. * @param rows an array of row data. * @param rowColumn The name of the row column. - * @param concentrationUnit The unit of the displayed concentration values. - * @param timeUnit The unit of the displayed time values. */ export const ResultsTable: FC = ({ groupIndex, @@ -102,15 +97,9 @@ export const ResultsTable: FC = ({ parameterIndex, rows = [], rowColumn = "", - concentrationUnit = "pmol/L", - timeUnit = "h", - children, }) => { const { groups } = useSubjectGroups(); - const parameters = useParameters({ - variableUnit: concentrationUnit, - timeUnit, - }); + const parameterNames = useParameterNames(); const concentrationVariables = useConcentrationVariables(); const [intervals] = useModelTimeIntervals(); const tableRows = useTableRows({ @@ -119,8 +108,6 @@ export const ResultsTable: FC = ({ intervalIndex, variableIndex, parameterIndex, - concentrationUnit, - timeUnit, }); if (!rows[0]) { @@ -130,7 +117,7 @@ export const ResultsTable: FC = ({ try { const columnHeadings = parameterIndex === "columns" - ? parameters.map((parameter) => parameter.name) + ? parameterNames : variableIndex === "columns" ? concentrationVariables.map((variable) => variable.name) : intervalIndex === "columns" @@ -168,7 +155,6 @@ export const ResultsTable: FC = ({ ))} - {children} ); // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/frontend-v2/src/features/results/columns.tsx b/frontend-v2/src/features/results/columns.tsx index fe7313ad..8f8d351c 100644 --- a/frontend-v2/src/features/results/columns.tsx +++ b/frontend-v2/src/features/results/columns.tsx @@ -57,8 +57,8 @@ export function columns({ return parameters.map((parameter) => { return { header: parameter.name, - value: (intervalIndex: number) => - parameter.value(intervalIndex, simulation, variable, aucVariable), + value: (interval: TimeIntervalRead) => + parameter.value(interval, simulation, variable, aucVariable), }; }); } diff --git a/frontend-v2/src/features/results/useParameters.tsx b/frontend-v2/src/features/results/useParameters.tsx index ba3d44ef..fe1b3279 100644 --- a/frontend-v2/src/features/results/useParameters.tsx +++ b/frontend-v2/src/features/results/useParameters.tsx @@ -17,7 +17,7 @@ import { useUnits } from "./useUnits"; export type Parameter = { name: string | JSX.Element; value: ( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, variable: VariableRead, aucVariable?: VariableRead, @@ -70,13 +70,14 @@ const variablePerInterval = ( intervals: TimeIntervalRead[], variable: VariableRead, simulation: SimulateResponse, - intervalIndex: number, + interval: TimeIntervalRead, ) => { const variableValuesPerInterval = valuesPerInterval( intervals, variable, simulation, ); + const intervalIndex = intervals.findIndex((i) => i.id === interval.id); const intervalValues = variableValuesPerInterval[intervalIndex]; const timePerInterval = timesPerInterval(simulation.time, intervals); const intervalTimes = timePerInterval[intervalIndex]; @@ -101,13 +102,23 @@ const timeOverUpperThresholdPerInterval = ( return timeOverThreshold(intervalTimes, intervalValues, threshold); }; -export function useParameters({ - variableUnit = "pmol/L", - timeUnit = "h", -}: { - variableUnit?: string; - timeUnit?: string; -}) { +/** + * Generate a list of secondary parameters to calculate for the current model. + * Each parameter consists of a name and a function to calculate the formatted value of the parameter + * from a time interval, simulation, and a model variable. + * ```js + * { + * name: "Min", + * value: (interval, simulation, variable) => { + * const [intervalValues] = variablePerInterval(intervals, variable, simulation, interval); + * const min = intervalValues ? Math.min(...intervalValues) : 0; + * return formattedNumber(min / variableConversionFactor(variable)); + * } + * } + * ``` + * @returns an array of Parameter objects. + */ +export function useParameters() { const units = useUnits(); const [baseIntervals] = useModelTimeIntervals(); const baseVariables = useVariables(); @@ -115,7 +126,9 @@ export function useParameters({ const variables = useNormalisedVariables(baseVariables); function variableConversionFactor(variable: VariableRead) { - const displayUnit = units?.find((unit) => unit.symbol === variableUnit); + const displayUnit = units?.find( + (unit) => unit.id === variable.threshold_unit, + ); const modelUnit = displayUnit?.compatible_units.find( (u) => +u.id === variable.unit, ); @@ -123,8 +136,8 @@ export function useParameters({ return conversionFactor; } - function timeConversionFactor() { - const displayUnit = units?.find((unit) => unit.symbol === timeUnit); + function timeConversionFactor(interval: TimeIntervalRead) { + const displayUnit = units?.find((unit) => unit.id === interval.unit); const modelUnit = displayUnit?.compatible_units.find( (u) => u.symbol === "h", ); @@ -136,7 +149,7 @@ export function useParameters({ { name: "Min", value( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, variable: VariableRead, ) { @@ -144,7 +157,7 @@ export function useParameters({ intervals, variable, simulation, - intervalIndex, + interval, ); const min = intervalValues ? Math.min(...intervalValues) : 0; @@ -154,7 +167,7 @@ export function useParameters({ { name: "Max", value( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, variable: VariableRead, ) { @@ -162,7 +175,7 @@ export function useParameters({ intervals, variable, simulation, - intervalIndex, + interval, ); const max = intervalValues ? Math.max(...intervalValues) : 0; return formattedNumber(max / variableConversionFactor(variable)); @@ -171,7 +184,7 @@ export function useParameters({ { name: "AUC", value( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, variable: VariableRead, ) { @@ -179,17 +192,13 @@ export function useParameters({ (v) => v.name === `calc_${variable.name}_AUC`, ); const [auc] = aucVariable - ? variablePerInterval( - intervals, - aucVariable, - simulation, - intervalIndex, - ) + ? variablePerInterval(intervals, aucVariable, simulation, interval) : []; const difference = auc ? auc[auc.length - 1] - auc[0] : 0; return formattedNumber( difference / - (variableConversionFactor(variable) * timeConversionFactor()), + (variableConversionFactor(variable) * + timeConversionFactor(interval)), ); }, }, @@ -200,7 +209,7 @@ export function useParameters({ ), value( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, baseVariable: VariableRead, ) { @@ -210,7 +219,7 @@ export function useParameters({ intervals, variable, simulation, - intervalIndex, + interval, ); const tLower = intervalValues ? timeOverLowerThresholdPerInterval( @@ -219,7 +228,7 @@ export function useParameters({ variable, ) : 0; - return formattedNumber(tLower / timeConversionFactor()); + return formattedNumber(tLower / timeConversionFactor(interval)); } }, }, @@ -230,7 +239,7 @@ export function useParameters({ ), value( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, baseVariable: VariableRead, ) { @@ -240,7 +249,7 @@ export function useParameters({ intervals, variable, simulation, - intervalIndex, + interval, ); const tUpper = intervalValues ? timeOverUpperThresholdPerInterval( @@ -249,7 +258,7 @@ export function useParameters({ variable, ) : 0; - return formattedNumber(tUpper / timeConversionFactor()); + return formattedNumber(tUpper / timeConversionFactor(interval)); } }, }, @@ -260,7 +269,7 @@ export function useParameters({ ), value( - intervalIndex: number, + interval: TimeIntervalRead, simulation: SimulateResponse, baseVariable: VariableRead, ) { @@ -270,7 +279,7 @@ export function useParameters({ intervals, variable, simulation, - intervalIndex, + interval, ); const tLower = intervalValues ? timeOverLowerThresholdPerInterval( @@ -286,9 +295,15 @@ export function useParameters({ variable, ) : 0; - return formattedNumber((tLower - tUpper) / timeConversionFactor()); + return formattedNumber( + (tLower - tUpper) / timeConversionFactor(interval), + ); } }, }, ] as Parameter[]; } + +export function useParameterNames() { + return useParameters().map((parameter) => parameter.name); +} diff --git a/frontend-v2/src/features/results/useTableRows.ts b/frontend-v2/src/features/results/useTableRows.ts index f57f4e72..0e1b2319 100644 --- a/frontend-v2/src/features/results/useTableRows.ts +++ b/frontend-v2/src/features/results/useTableRows.ts @@ -16,8 +16,6 @@ interface TableRowsProps { intervalIndex: FilterIndex; variableIndex: FilterIndex; parameterIndex: FilterIndex; - concentrationUnit?: string; - timeUnit?: string; } export function useTableRows({ rows, @@ -25,15 +23,10 @@ export function useTableRows({ intervalIndex, variableIndex, parameterIndex, - concentrationUnit = "pmol/L", - timeUnit = "h", }: TableRowsProps) { const units = useUnits(); const variables = useVariables(); - const parameters = useParameters({ - variableUnit: concentrationUnit, - timeUnit, - }); + const parameters = useParameters(); const concentrationVariables = useConcentrationVariables(); const { simulations } = useContext(SimulationContext); const [intervals] = useModelTimeIntervals(); diff --git a/frontend-v2/src/features/results/utils.ts b/frontend-v2/src/features/results/utils.ts index 729c025c..dc1976e6 100644 --- a/frontend-v2/src/features/results/utils.ts +++ b/frontend-v2/src/features/results/utils.ts @@ -210,7 +210,7 @@ export function tableRow({ }); const values = tableColumns.map((column, index) => { return column.value( - interval ? intervals.indexOf(interval) : index, + interval ? interval : intervals[index], simulation ? simulation : simulations[index], variable ? variable : concentrationVariables[index], );