diff --git a/frontend/src/components/BarChart.tsx b/frontend/src/components/Charts/BarChart.tsx similarity index 99% rename from frontend/src/components/BarChart.tsx rename to frontend/src/components/Charts/BarChart.tsx index e35efc44..b08356ad 100644 --- a/frontend/src/components/BarChart.tsx +++ b/frontend/src/components/Charts/BarChart.tsx @@ -8,7 +8,7 @@ import { Button } from "@equinor/eds-core-react"; ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, zoomPlugin); -interface BarChartProps { +export interface BarChartProps { graphData: ChartDataSet[]; aspectRatio?: number; xLabel?: string; diff --git a/frontend/src/components/LineChart.tsx b/frontend/src/components/Charts/LineChart.tsx similarity index 98% rename from frontend/src/components/LineChart.tsx rename to frontend/src/components/Charts/LineChart.tsx index 2fc5d953..7bee2b42 100644 --- a/frontend/src/components/LineChart.tsx +++ b/frontend/src/components/Charts/LineChart.tsx @@ -22,7 +22,7 @@ export interface LineSeries { color?: string; } -interface LineChartProps { +export interface LineChartProps { xValues: number[]; series: LineSeries[]; xAxisLabel?: string; diff --git a/frontend/src/components/ScatterPlot.tsx b/frontend/src/components/Charts/ScatterChart.tsx similarity index 94% rename from frontend/src/components/ScatterPlot.tsx rename to frontend/src/components/Charts/ScatterChart.tsx index 7218113b..7b726226 100644 --- a/frontend/src/components/ScatterPlot.tsx +++ b/frontend/src/components/Charts/ScatterChart.tsx @@ -11,14 +11,14 @@ export interface ScatterDataSet { color?: string; } -interface ScatterPlotProps { +export interface ScatterChartProps { datasets: ScatterDataSet[]; xLabel?: string; yLabel?: string; showDiagonal?: boolean; } -const ScatterPlot: React.FC = ({ datasets, xLabel, yLabel, showDiagonal }) => { +const ScatterChart: React.FC = ({ datasets, xLabel, yLabel, showDiagonal }) => { if (datasets.length === 0) return null; const maxVal = Math.max(...datasets.flatMap((ds) => ds.data.flatMap((p) => [Math.abs(p.x), Math.abs(p.y)])), 0); @@ -101,4 +101,4 @@ const ScatterPlot: React.FC = ({ datasets, xLabel, yLabel, sho ); }; -export default ScatterPlot; +export default ScatterChart; diff --git a/frontend/src/components/GridSimulation/CompareGridSimulations.tsx b/frontend/src/components/Comparison/CompareGridSimulations.tsx similarity index 76% rename from frontend/src/components/GridSimulation/CompareGridSimulations.tsx rename to frontend/src/components/Comparison/CompareGridSimulations.tsx index f1f12c48..368b256c 100644 --- a/frontend/src/components/GridSimulation/CompareGridSimulations.tsx +++ b/frontend/src/components/Comparison/CompareGridSimulations.tsx @@ -1,36 +1,37 @@ -import React, { useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { useQueries, type Query } from "@tanstack/react-query"; -import { CircularProgress, NativeSelect, Typography } from "@equinor/eds-core-react"; +import { NativeSelect, Typography } from "@equinor/eds-core-react"; import { getGridSimulationResult } from "@/api/api"; -import { MainContainer } from "@/components/styles"; -import { GridSimulationResult } from "@/dto/GridSimulation"; -import LineChart, { LineSeries } from "@/components/LineChart"; +import { getGridAxis, getGridInput, GridSimulationResult } from "@/dto/GridSimulation"; +import { Phase } from "@/dto/SimulationResults"; +import LineChart, { LineSeries } from "@/components/Charts/LineChart"; import { collectOutputSubstances, pointOutput, significantSubstances, visiblePhaseKinds, -} from "@/functions/GridSimulation"; +} from "@/components/GridSimulation/gridSimulationUtils"; import { optionName } from "@/functions/Substance"; import { useAvailableModels } from "@/contexts/ModelContext"; import { buildModelSections, phaseLabel } from "@/utils/modelUtils"; import ModelAccordionLayout, { AccordionItem } from "@/components/ModelAccordionLayout"; -import ConcentrationTable, { SimulationConcentrations } from "@/components/ConcentrationTable"; +import ConcentrationTable, { SimulationConcentrations } from "@/components/Tables/ConcentrationTable"; +import ComparisonPage from "@/components/Comparison/ComparisonPage"; interface CompareGridSimulationsProps { gridIds: string[]; } const modelChainLabel = (result: GridSimulationResult): string => { - const firstSim = result.simulations[0]; - if (!firstSim) return "Unknown model"; - return firstSim.input.models.map((m) => m.modelId).join(" → ") || "Unknown model"; + const input = getGridInput(result); + if (!input) return "Unknown model"; + return input.models.map((m) => m.modelId).join(" → ") || "Unknown model"; }; interface CompareSectionProps { results: GridSimulationResult[]; modelIndex: number; - phaseKind: string; + phaseKind: Phase["kind"]; } const CompareSection: React.FC = ({ results, modelIndex, phaseKind }) => { @@ -50,7 +51,8 @@ const CompareSection: React.FC = ({ results, modelIndex, ph const [substance, setSubstance] = useState(""); const selectedSubstance = substance || allSubstances[0] || ""; - const xAxisSubstance = results[0]?.axes[0]?.substance ?? ""; + const referenceResult = results.at(0); + const xAxisSubstance = referenceResult ? (getGridAxis(referenceResult)?.substance ?? "") : ""; const unifiedXValues = Array.from( new Set(results.flatMap((r) => r.simulations.map((sim) => sim.input.concentrations[xAxisSubstance] ?? 0))) ).sort((a, b) => a - b); @@ -58,7 +60,7 @@ const CompareSection: React.FC = ({ results, modelIndex, ph const series: LineSeries[] = results.map((r) => { const valueToSim = new Map(r.simulations.map((sim) => [sim.input.concentrations[xAxisSubstance] ?? 0, sim])); return { - label: `${modelChainLabel(r)} · ${r.axes[0]?.substance ?? ""}`, + label: `${modelChainLabel(r)} · ${getGridAxis(r)?.substance ?? ""}`, data: unifiedXValues.map((x) => { const sim = valueToSim.get(x); return sim ? pointOutput(sim, selectedSubstance, modelIndex, phaseKind) : null; @@ -66,7 +68,7 @@ const CompareSection: React.FC = ({ results, modelIndex, ph }; }); - const axisSubstances = new Set(results.map((r) => r.axes[0]?.substance).filter(Boolean)); + const axisSubstances = new Set(results.map((r) => getGridAxis(r)?.substance).filter(Boolean)); const xAxisLabel = axisSubstances.size === 1 ? `${[...axisSubstances][0]} (ppm·mol)` : "Varied concentration (ppm·mol)"; const unit = phaseKind === "aqueous" ? "wt%" : "ppm·mol"; @@ -118,52 +120,19 @@ const CompareGridSimulations: React.FC = ({ gridIds const isLoading = queries.some((q) => q.isLoading); const hasError = queries.some((q) => q.isError); const results = queries.map((q) => q.data).filter((data): data is GridSimulationResult => data !== undefined); + const referenceResult = results.at(0); - const header = ( - - Compare Grid Simulations - - ); - - if (gridIds.length === 0) { - return ( - - {header} - No grid simulations selected for comparison. - - ); - } - if (isLoading) { - return ( - - {header} - - - ); - } - if (hasError) { - return ( - - {header} - - Error loading grid simulation results - - - ); - } - - const firstSim = results[0]?.simulations[0]; - const inputModels = firstSim?.input.models ?? []; + const inputModels = referenceResult ? (getGridInput(referenceResult)?.models ?? []) : []; const sections = buildModelSections(inputModels, models); - const allPhasesByModel = new Map(); + const allPhasesByModel = new Map(); sections.forEach((section) => { section.indices.forEach((modelIndex) => { - const phases = new Set(); + const phases = new Set(); results.forEach((r) => { visiblePhaseKinds(r.simulations, modelIndex).forEach((k) => phases.add(k)); }); - const order = ["co2-rich", "aqueous"]; + const order: Phase["kind"][] = ["co2-rich", "aqueous"]; allPhasesByModel.set( modelIndex, order.filter((k) => phases.has(k)) @@ -201,7 +170,7 @@ const CompareGridSimulations: React.FC = ({ gridIds return { id: gridIds[index], modelName: modelChainLabel(result), - concentrations: result.simulations[0]?.input.concentrations ?? {}, + concentrations: getGridInput(result)?.concentrations ?? {}, ranges, }; }); @@ -211,9 +180,14 @@ const CompareGridSimulations: React.FC = ({ gridIds ).sort(); return ( - - {header} - + Input Concentrations @@ -223,7 +197,7 @@ const CompareGridSimulations: React.FC = ({ gridIds - + ); }; diff --git a/frontend/src/components/Comparison/CompareSimulations.tsx b/frontend/src/components/Comparison/CompareSimulations.tsx new file mode 100644 index 00000000..1a0e3c67 --- /dev/null +++ b/frontend/src/components/Comparison/CompareSimulations.tsx @@ -0,0 +1,120 @@ +import React from "react"; +import { useQueries } from "@tanstack/react-query"; +import { Banner, Typography } from "@equinor/eds-core-react"; +import { getResultForSimulation, ResultIsPending } from "@/api/api"; +import BarChart from "@/components/Charts/BarChart"; +import ConcentrationTable from "@/components/Tables/ConcentrationTable"; +import { ChartDataSet } from "@/dto/ChartData"; +import { getOutputConcentrations, getPrimaryModel, SimulationResults } from "@/dto/SimulationResults"; +import ComparisonPage from "@/components/Comparison/ComparisonPage"; + +type SimulationComparison = { + id: string; + modelName: string; + inputConcentrations: Record; + outputConcentrations: Record; +}; + +interface CompareSimulationsProps { + simulationIds: string[]; +} + +const CompareSimulations: React.FC = ({ simulationIds }) => { + const queries = useQueries({ + queries: simulationIds.map((id) => ({ + queryKey: ["simulation", id], + queryFn: () => getResultForSimulation(id), + retry: (_count: number, error: Error) => error instanceof ResultIsPending, + retryDelay: () => 2000, + })), + }); + + const isLoading = queries.some((query) => query.isLoading); + const hasError = queries.some((query) => query.isError); + + const simulationResults = queries + .map((query) => query.data) + .filter((result): result is SimulationResults => result !== undefined); + const comparisons: SimulationComparison[] = simulationResults.map((result, index) => ({ + id: simulationIds[index], + modelName: getPrimaryModel(result)?.modelId ?? "Unknown", + inputConcentrations: result.input.concentrations, + outputConcentrations: getOutputConcentrations(result), + })); + + const firstInput = comparisons[0] ? JSON.stringify(Object.entries(comparisons[0].inputConcentrations).sort()) : ""; + const allInputsMatch = + comparisons.length < 2 || + comparisons.every( + (comparison) => JSON.stringify(Object.entries(comparison.inputConcentrations).sort()) === firstInput + ); + + const collectUniqueSubstances = ( + field: keyof Pick + ) => Array.from(new Set(comparisons.flatMap((comparison) => Object.keys(comparison[field])))).sort(); + + const inputSubstances = collectUniqueSubstances("inputConcentrations"); + const significantOutputs = collectUniqueSubstances("outputConcentrations").filter((substance) => + comparisons.some((comparison) => (comparison.outputConcentrations[substance] ?? 0) >= 0.01) + ); + const chartData: ChartDataSet[] = comparisons.map((comparison) => ({ + label: `${comparison.modelName} (${comparison.id.slice(0, 8)})`, + data: significantOutputs.map((substance) => ({ + x: substance, + y: comparison.outputConcentrations[substance] ?? 0, + })), + })); + + return ( + + {!allInputsMatch && ( + + ⚠️ + + Warning: The selected simulations have different input concentrations. You may be comparing + incompatible scenarios. + + + )} + + + Input Concentrations + + ({ + id: comparison.id, + modelName: comparison.modelName, + concentrations: comparison.inputConcentrations, + }))} + highlightDifferences + /> + + + Output Concentrations + + + + + Concentration Values (≥ 0.01) + + ({ + id: comparison.id, + modelName: comparison.modelName, + concentrations: comparison.outputConcentrations, + }))} + /> + + ); +}; + +export default CompareSimulations; diff --git a/frontend/src/components/Comparison/ComparisonPage.tsx b/frontend/src/components/Comparison/ComparisonPage.tsx new file mode 100644 index 00000000..b04b3dc2 --- /dev/null +++ b/frontend/src/components/Comparison/ComparisonPage.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { CircularProgress, Typography } from "@equinor/eds-core-react"; +import { MainContainer } from "@/components/styles"; + +interface ComparisonPageProps { + title: string; + isEmpty: boolean; + emptyMessage: string; + isLoading: boolean; + hasError: boolean; + errorMessage: string; + children: React.ReactNode; +} + +const ComparisonPage: React.FC = ({ + title, + isEmpty, + emptyMessage, + isLoading, + hasError, + errorMessage, + children, +}) => { + let content = children; + if (isEmpty) { + content = {emptyMessage}; + } else if (isLoading) { + content = ; + } else if (hasError) { + content = ( + + {errorMessage} + + ); + } + + return ( + + + {title} + + {content} + + ); +}; + +export default ComparisonPage; diff --git a/frontend/src/components/GridSimulation/GridPhaseTable.tsx b/frontend/src/components/GridSimulation/GridPhaseTable.tsx new file mode 100644 index 00000000..6cd7a0f2 --- /dev/null +++ b/frontend/src/components/GridSimulation/GridPhaseTable.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { Table } from "@equinor/eds-core-react"; +import { GridSimulationResult } from "@/dto/GridSimulation"; +import { Phase } from "@/dto/SimulationResults"; +import { formatConcentration } from "@/functions/Formatting"; +import { pointOutput } from "@/components/GridSimulation/gridSimulationUtils"; +import { optionName } from "@/functions/Substance"; + +interface GridPhaseTableProps { + result: GridSimulationResult; + modelIndex: number; + phaseKind: Phase["kind"]; + substances: string[]; +} + +const GridPhaseTable: React.FC = ({ result, modelIndex, phaseKind, substances }) => ( + + + + {result.axes.map((axis) => ( + {axis.substance} (ppm) + ))} + {substances.map((substance) => ( + {optionName(substance)} + ))} + + + + {result.simulations.map((simulation, index) => ( + + {result.axes.map((axis) => ( + + {simulation.input.concentrations[axis.substance] ?? 0} + + ))} + {substances.map((substance) => ( + + {simulation.status === "done" + ? formatConcentration(pointOutput(simulation, substance, modelIndex, phaseKind) ?? 0) + : simulation.status === "error" + ? "error" + : "…"} + + ))} + + ))} + +
+); + +export default GridPhaseTable; diff --git a/frontend/src/components/GridSimulation/GridResultStep.tsx b/frontend/src/components/GridSimulation/GridResultStep.tsx index 82f856b5..2504d057 100644 --- a/frontend/src/components/GridSimulation/GridResultStep.tsx +++ b/frontend/src/components/GridSimulation/GridResultStep.tsx @@ -2,8 +2,8 @@ import React from "react"; import { Banner } from "@equinor/eds-core-react"; import { GridSimulationResult } from "@/dto/GridSimulation"; import GridResults from "./GridResults"; -import Working from "@/components/Simulation/Working"; -import NoResults from "@/components/Simulation/NoResults"; +import Working from "@/components/SimulationStatus/Working"; +import NoResults from "@/components/SimulationStatus/NoResults"; type GridResultStepProps = { result?: GridSimulationResult; diff --git a/frontend/src/components/GridSimulation/GridResults.tsx b/frontend/src/components/GridSimulation/GridResults.tsx index 2ebe5020..0fb7e5de 100644 --- a/frontend/src/components/GridSimulation/GridResults.tsx +++ b/frontend/src/components/GridSimulation/GridResults.tsx @@ -1,20 +1,21 @@ import React, { useMemo, useState } from "react"; -import { Autocomplete, Banner, Table, Typography } from "@equinor/eds-core-react"; -import { GridSimulationResult } from "@/dto/GridSimulation"; -import { formatConcentration } from "@/functions/Formatting"; +import { Autocomplete, Banner, Typography } from "@equinor/eds-core-react"; +import { getGridAxis, getGridInput, GridSimulationResult } from "@/dto/GridSimulation"; +import { Phase } from "@/dto/SimulationResults"; import { buildGridCsv, collectOutputSubstances, defaultSelectedSubstances, pointOutput, visiblePhaseKinds, -} from "@/functions/GridSimulation"; +} from "@/components/GridSimulation/gridSimulationUtils"; import { optionName } from "@/functions/Substance"; -import LineChart, { LineSeries } from "@/components/LineChart"; +import LineChart, { LineSeries } from "@/components/Charts/LineChart"; import DownloadButton from "@/components/DownloadButton"; import { useAvailableModels } from "@/contexts/ModelContext"; import { buildModelSections, phaseLabel } from "@/utils/modelUtils"; import ModelAccordionLayout, { AccordionItem } from "@/components/ModelAccordionLayout"; +import GridPhaseTable from "@/components/GridSimulation/GridPhaseTable"; interface GridResultsProps { result: GridSimulationResult; @@ -23,11 +24,11 @@ interface GridResultsProps { interface GridPhaseChartProps { result: GridSimulationResult; modelIndex: number; - phaseKind: string; + phaseKind: Phase["kind"]; } const GridPhaseChart: React.FC = ({ result, modelIndex, phaseKind }) => { - const { simulations, axes } = result; + const { simulations } = result; const allSubstances = useMemo( () => collectOutputSubstances(simulations, modelIndex, phaseKind), @@ -36,7 +37,7 @@ const GridPhaseChart: React.FC = ({ result, modelIndex, pha const [selection, setSelection] = useState(null); const selectedSubstances = selection ?? defaultSelectedSubstances(simulations, modelIndex, phaseKind); - const xAxisSubstance = axes[0]?.substance ?? "Unknown"; + const xAxisSubstance = getGridAxis(result)?.substance ?? "Unknown"; const xValues = simulations.map((sim) => parseFloat(sim.input.concentrations[xAxisSubstance].toFixed(2)) ?? 0); const series: LineSeries[] = selectedSubstances.map((substance) => ({ label: optionName(substance), @@ -57,7 +58,7 @@ const GridPhaseChart: React.FC = ({ result, modelIndex, pha {series.length === 0 ? ( - Select at least one output substance to plot. + Select at least one output substance to chart. ) : ( = ({ result, modelIndex, pha Values - - - - {axes.map((axis) => ( - {axis.substance} (ppm) - ))} - {selectedSubstances.map((substance) => ( - {optionName(substance)} - ))} - - - - {simulations.map((sim, idx) => ( - - {axes.map((axis) => ( - - {sim.input.concentrations[axis.substance] ?? 0} - - ))} - {selectedSubstances.map((substance) => ( - - {sim.status === "done" - ? formatConcentration(pointOutput(sim, substance, modelIndex, phaseKind) ?? 0) - : sim.status === "error" - ? "error" - : "…"} - - ))} - - ))} - -
+ ); }; @@ -136,11 +111,10 @@ const GridSection: React.FC = ({ result, modelIndex }) => { }; const GridResults: React.FC = ({ result }) => { - const { simulations, axes } = result; + const { simulations } = result; const { models } = useAvailableModels(); - const firstSim = simulations[0]; - const inputModels = firstSim?.input.models ?? []; + const inputModels = getGridInput(result)?.models ?? []; const sections = buildModelSections(inputModels, models); @@ -148,7 +122,7 @@ const GridResults: React.FC = ({ result }) => { const doneSimulations = simulations.filter((sim) => sim.status === "done"); const allFinished = doneSimulations.length + erroredSimulations.length === simulations.length; - const xAxisSubstance = axes[0]?.substance ?? "Unknown"; + const xAxisSubstance = getGridAxis(result)?.substance ?? "Unknown"; const modelLabel = inputModels.map((model) => model.modelId).join(" → ") || "Unknown model"; const items: AccordionItem[] = sections.flatMap((section) => diff --git a/frontend/src/functions/GridSimulation.ts b/frontend/src/components/GridSimulation/gridSimulationUtils.ts similarity index 75% rename from frontend/src/functions/GridSimulation.ts rename to frontend/src/components/GridSimulation/gridSimulationUtils.ts index 337b1981..2ebfae9e 100644 --- a/frontend/src/functions/GridSimulation.ts +++ b/frontend/src/components/GridSimulation/gridSimulationUtils.ts @@ -1,24 +1,30 @@ import { GridSimulationResult } from "@/dto/GridSimulation"; -import { SimulationResults, getCo2RichConcentrations, Phase } from "@/dto/SimulationResults"; +import { + SimulationResults, + getFinalResult, + getModelResult, + getPhaseConcentrations, + Phase, +} from "@/dto/SimulationResults"; import { TabulatedResultRow } from "@/dto/ChartData"; import { convertTabulatedDataToCSVFormat } from "@/functions/Formatting"; import { ppmMolToWeightPercent } from "@/functions/UnitConversion"; -export const SIGNIFICANCE_THRESHOLD = 0.01; +const SIGNIFICANCE_THRESHOLD = 0.01; export const MAX_DEFAULT_SERIES = 6; const getModelPhases = (sim: SimulationResults, modelIndex: number): Phase[] => { if (sim.status !== "done" || sim.results.length === 0) return []; - return sim.results[modelIndex]?.phases ?? []; + return getModelResult(sim, modelIndex)?.phases ?? []; }; -const getPhaseConcentrations = ( +const getModelPhaseConcentrations = ( sim: SimulationResults, modelIndex: number, - phaseKind: string + phaseKind: Phase["kind"] ): Record => { const phases = getModelPhases(sim, modelIndex); - const raw = phases.find((p) => p.kind === phaseKind)?.concentrations ?? {}; + const raw = getPhaseConcentrations(phases, phaseKind); if (phaseKind === "aqueous") { return ppmMolToWeightPercent(raw); } @@ -27,22 +33,21 @@ const getPhaseConcentrations = ( const lastPhaseConcentrations = (sim: SimulationResults): Record => { if (sim.status !== "done" || sim.results.length === 0) return {}; - const lastResult = sim.results[sim.results.length - 1]; - return getCo2RichConcentrations(lastResult.phases); + return getPhaseConcentrations(getFinalResult(sim)?.phases, "co2-rich"); }; export const pointOutput = ( sim: SimulationResults, substance: string, modelIndex?: number, - phaseKind?: string + phaseKind?: Phase["kind"] ): number | null => { if (sim.status !== "done") return null; if (modelIndex !== undefined && phaseKind !== undefined) { - return getPhaseConcentrations(sim, modelIndex, phaseKind)[substance] ?? 0; + return getModelPhaseConcentrations(sim, modelIndex, phaseKind)[substance] ?? 0; } if (modelIndex !== undefined) { - return getCo2RichConcentrations(getModelPhases(sim, modelIndex))[substance] ?? 0; + return getPhaseConcentrations(getModelPhases(sim, modelIndex), "co2-rich")[substance] ?? 0; } return lastPhaseConcentrations(sim)[substance] ?? 0; }; @@ -50,14 +55,16 @@ export const pointOutput = ( export const collectOutputSubstances = ( simulations: SimulationResults[], modelIndex?: number, - phaseKind?: string + phaseKind?: Phase["kind"] ): string[] => { const substances = new Set(); simulations.forEach((sim) => { if (modelIndex !== undefined && phaseKind !== undefined) { - Object.keys(getPhaseConcentrations(sim, modelIndex, phaseKind)).forEach((s) => substances.add(s)); + Object.keys(getModelPhaseConcentrations(sim, modelIndex, phaseKind)).forEach((s) => substances.add(s)); } else if (modelIndex !== undefined) { - Object.keys(getCo2RichConcentrations(getModelPhases(sim, modelIndex))).forEach((s) => substances.add(s)); + Object.keys(getPhaseConcentrations(getModelPhases(sim, modelIndex), "co2-rich")).forEach((s) => + substances.add(s) + ); } else { Object.keys(lastPhaseConcentrations(sim)).forEach((s) => substances.add(s)); } @@ -69,7 +76,7 @@ export const maxOutput = ( simulations: SimulationResults[], substance: string, modelIndex?: number, - phaseKind?: string + phaseKind?: Phase["kind"] ): number => simulations.reduce((currentMax, sim) => { const val = pointOutput(sim, substance, modelIndex, phaseKind); @@ -79,7 +86,7 @@ export const maxOutput = ( export const significantSubstances = ( simulations: SimulationResults[], modelIndex?: number, - phaseKind?: string + phaseKind?: Phase["kind"] ): string[] => collectOutputSubstances(simulations, modelIndex, phaseKind).filter( (s) => maxOutput(simulations, s, modelIndex, phaseKind) >= SIGNIFICANCE_THRESHOLD @@ -88,7 +95,7 @@ export const significantSubstances = ( export const defaultSelectedSubstances = ( simulations: SimulationResults[], modelIndex?: number, - phaseKind?: string + phaseKind?: Phase["kind"] ): string[] => significantSubstances(simulations, modelIndex, phaseKind) .sort( @@ -97,8 +104,8 @@ export const defaultSelectedSubstances = ( ) .slice(0, MAX_DEFAULT_SERIES); -export const visiblePhaseKinds = (simulations: SimulationResults[], modelIndex: number): string[] => { - const kinds = new Set(); +export const visiblePhaseKinds = (simulations: SimulationResults[], modelIndex: number): Phase["kind"][] => { + const kinds = new Set(); simulations.forEach((sim) => { getModelPhases(sim, modelIndex).forEach((phase) => { if (Object.values(phase.concentrations).some((v) => v > 0)) { @@ -106,7 +113,7 @@ export const visiblePhaseKinds = (simulations: SimulationResults[], modelIndex: } }); }); - const order = ["co2-rich", "aqueous"]; + const order: Phase["kind"][] = ["co2-rich", "aqueous"]; return order.filter((k) => kinds.has(k)); }; diff --git a/frontend/src/components/LabResultsPlot.tsx b/frontend/src/components/LabResults/LabResultsChart.tsx similarity index 82% rename from frontend/src/components/LabResultsPlot.tsx rename to frontend/src/components/LabResults/LabResultsChart.tsx index 11670d33..fb9e49c6 100644 --- a/frontend/src/components/LabResultsPlot.tsx +++ b/frontend/src/components/LabResults/LabResultsChart.tsx @@ -1,22 +1,22 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { Typography } from "@equinor/eds-core-react"; import { ExperimentResult } from "@/dto/ExperimentResult"; import { ChartDataSet } from "@/dto/ChartData"; -import BarChart from "./BarChart"; +import BarChart from "@/components/Charts/BarChart"; import { SimulationResults } from "@/dto/SimulationResults"; import { convertSimulationToChartData } from "@/functions/Formatting"; import { getLabResultColor, EXPERIMENT_PATTERNS } from "@/functions/Colors"; -interface LabResultsPlotProps { +interface LabResultsChartProps { selectedExperiments: ExperimentResult[]; simulationsPerExperiment: Record; } -const LabResultsPlot: React.FC = ({ +const LabResultsChart = ({ selectedExperiments, simulationsPerExperiment: simulationQueries, -}) => { - const [plotComponents, setPlotComponents] = useState([]); +}: LabResultsChartProps) => { + const [chartComponents, setChartComponents] = useState([]); const chartDatasets: ChartDataSet[] = []; selectedExperiments.forEach((exp, expIdx) => { @@ -44,7 +44,7 @@ const LabResultsPlot: React.FC = ({ if (selectedExperiments.length === 0) { return ( - Select experiments from the table below to view comparison plots. + Select experiments from the table below to view comparison charts. ); } @@ -54,21 +54,21 @@ const LabResultsPlot: React.FC = ({ ({ ...ds, - data: ds.data.filter((point) => plotComponents.length === 0 || plotComponents.includes(point.x)), + data: ds.data.filter((point) => chartComponents.length === 0 || chartComponents.includes(point.x)), }))} aspectRatio={4} yLabel="Concentration (ppm·mol)" xLabel="Components" />
- Plot subset of components:{" "} + Chart subset of components:{" "} {allComponents.map((component) => (