Skip to content

Commit d61e14f

Browse files
Fix comparison for gridsimulation
1 parent b55ca08 commit d61e14f

1 file changed

Lines changed: 148 additions & 54 deletions

File tree

frontend/src/components/GridSimulation/CompareGridSimulations.tsx

Lines changed: 148 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import React, { useMemo, useState } from "react";
22
import { useQueries } from "@tanstack/react-query";
3-
import { CircularProgress, NativeSelect, Typography } from "@equinor/eds-core-react";
3+
import { Accordion, CircularProgress, NativeSelect, Typography } from "@equinor/eds-core-react";
44
import { getGridSimulationResult, ResultIsPending } from "@/api/api";
55
import { MainContainer } from "@/components/styles";
66
import { GridSimulationResult } from "@/dto/GridSimulation";
77
import LineChart, { LineSeries } from "@/components/LineChart";
8-
import { collectOutputSubstances, pointOutput, significantSubstances } from "@/functions/GridSimulation";
8+
import {
9+
collectOutputSubstances,
10+
pointOutput,
11+
significantSubstances,
12+
visiblePhaseKinds,
13+
} from "@/functions/GridSimulation";
914
import { optionName } from "@/functions/Substance";
15+
import { useAvailableModels } from "@/contexts/ModelContext";
16+
import { buildModelSections } from "@/utils/modelUtils";
1017

1118
interface CompareGridSimulationsProps {
1219
gridIds: string[];
@@ -18,7 +25,88 @@ const modelChainLabel = (result: GridSimulationResult): string => {
1825
return firstSim.input.models.map((m) => m.modelId).join(" → ") || "Unknown model";
1926
};
2027

28+
function phaseLabel(kind: string): string {
29+
return kind === "aqueous" ? "Aqueous" : "CO2-rich";
30+
}
31+
32+
interface CompareSectionProps {
33+
results: GridSimulationResult[];
34+
modelIndex: number;
35+
phaseKind: string;
36+
}
37+
38+
const CompareSection: React.FC<CompareSectionProps> = ({ results, modelIndex, phaseKind }) => {
39+
const allSubstances = useMemo(() => {
40+
const substances = new Set<string>();
41+
results.forEach((r) =>
42+
significantSubstances(r.simulations, modelIndex, phaseKind).forEach((s) => substances.add(s))
43+
);
44+
if (substances.size === 0) {
45+
results.forEach((r) =>
46+
collectOutputSubstances(r.simulations, modelIndex, phaseKind).forEach((s) => substances.add(s))
47+
);
48+
}
49+
return Array.from(substances).sort();
50+
}, [results, modelIndex, phaseKind]);
51+
52+
const [substance, setSubstance] = useState<string>("");
53+
const selectedSubstance = substance || allSubstances[0] || "";
54+
55+
const xAxisSubstance = results[0]?.axes[0]?.substance ?? "";
56+
const unifiedXValues = Array.from(
57+
new Set(results.flatMap((r) => r.simulations.map((sim) => sim.input.concentrations[xAxisSubstance] ?? 0)))
58+
).sort((a, b) => a - b);
59+
60+
const series: LineSeries[] = results.map((r) => {
61+
const valueToSim = new Map(r.simulations.map((sim) => [sim.input.concentrations[xAxisSubstance] ?? 0, sim]));
62+
return {
63+
label: `${modelChainLabel(r)} · ${r.axes[0]?.substance ?? ""}`,
64+
data: unifiedXValues.map((x) => {
65+
const sim = valueToSim.get(x);
66+
return sim ? pointOutput(sim, selectedSubstance, modelIndex, phaseKind) : null;
67+
}),
68+
};
69+
});
70+
71+
const axisSubstances = new Set(results.map((r) => r.axes[0]?.substance).filter(Boolean));
72+
const xAxisLabel = axisSubstances.size === 1 ? `${[...axisSubstances][0]} (ppm)` : "Varied concentration (ppm)";
73+
const unit = phaseKind === "aqueous" ? "wt%" : "ppm";
74+
75+
return (
76+
<>
77+
<NativeSelect
78+
id={`compare-${modelIndex}-${phaseKind}`}
79+
label="Output substance"
80+
value={selectedSubstance}
81+
onChange={(e) => setSubstance(e.target.value)}
82+
style={{ maxWidth: "400px", marginBottom: "1rem" }}
83+
>
84+
{allSubstances.map((s) => (
85+
<option key={s} value={s}>
86+
{optionName(s)}
87+
</option>
88+
))}
89+
</NativeSelect>
90+
{selectedSubstance === "" ? (
91+
<Typography variant="body_short" italic>
92+
No output substances available to compare.
93+
</Typography>
94+
) : (
95+
<LineChart
96+
xValues={unifiedXValues}
97+
series={series}
98+
xAxisLabel={xAxisLabel}
99+
yAxisLabel={`${selectedSubstance} (${unit})`}
100+
aspectRatio={2}
101+
/>
102+
)}
103+
</>
104+
);
105+
};
106+
21107
const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds }) => {
108+
const { models } = useAvailableModels();
109+
22110
const queries = useQueries({
23111
queries: gridIds.map((id) => ({
24112
queryKey: ["grid-simulation", id],
@@ -32,18 +120,6 @@ const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds
32120
const hasError = queries.some((q) => q.isError);
33121
const results = queries.map((q) => q.data).filter((data): data is GridSimulationResult => data !== undefined);
34122

35-
const allSubstances = useMemo(() => {
36-
const substances = new Set<string>();
37-
results.forEach((r) => significantSubstances(r.simulations).forEach((s) => substances.add(s)));
38-
if (substances.size === 0) {
39-
results.forEach((r) => collectOutputSubstances(r.simulations).forEach((s) => substances.add(s)));
40-
}
41-
return Array.from(substances).sort();
42-
}, [results]);
43-
44-
const [substance, setSubstance] = useState<string>("");
45-
const selectedSubstance = substance || allSubstances[0] || "";
46-
47123
const header = (
48124
<Typography variant="h2" style={{ marginBottom: "2rem" }}>
49125
Compare Grid Simulations
@@ -77,24 +153,51 @@ const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds
77153
);
78154
}
79155

80-
const xAxisSubstance = results[0]?.axes[0]?.substance ?? "";
81-
const unifiedXValues = Array.from(
82-
new Set(results.flatMap((r) => r.simulations.map((sim) => sim.input.concentrations[xAxisSubstance] ?? 0)))
83-
).sort((a, b) => a - b);
156+
const firstSim = results[0]?.simulations[0];
157+
const inputModels = firstSim?.input.models ?? [];
158+
const sections = buildModelSections(inputModels, models);
84159

85-
const series: LineSeries[] = results.map((r) => {
86-
const valueToSim = new Map(r.simulations.map((sim) => [sim.input.concentrations[xAxisSubstance] ?? 0, sim]));
87-
return {
88-
label: `${modelChainLabel(r)} · ${r.axes[0]?.substance ?? ""}`,
89-
data: unifiedXValues.map((x) => {
90-
const sim = valueToSim.get(x);
91-
return sim ? pointOutput(sim, selectedSubstance) : null;
92-
}),
93-
};
160+
const allPhasesByModel = new Map<number, string[]>();
161+
sections.forEach((section) => {
162+
section.indices.forEach((modelIndex) => {
163+
const phases = new Set<string>();
164+
results.forEach((r) => {
165+
visiblePhaseKinds(r.simulations, modelIndex).forEach((k) => phases.add(k));
166+
});
167+
const order = ["co2-rich", "aqueous"];
168+
allPhasesByModel.set(
169+
modelIndex,
170+
order.filter((k) => phases.has(k))
171+
);
172+
});
94173
});
95174

96-
const axisSubstances = new Set(results.map((r) => r.axes[0]?.substance).filter(Boolean));
97-
const xAxisLabel = axisSubstances.size === 1 ? `${[...axisSubstances][0]} (ppm)` : "Varied concentration (ppm)";
175+
const allAccordions: { key: string; header: string; modelIndex: number; phaseKind: string }[] = [];
176+
sections.forEach((section) => {
177+
section.indices.forEach((modelIndex) => {
178+
const phases = allPhasesByModel.get(modelIndex) ?? [];
179+
const modelName =
180+
models.find((m) => m.modelId === inputModels[modelIndex]?.modelId)?.displayName ??
181+
inputModels[modelIndex]?.modelId;
182+
if (phases.length === 1) {
183+
allAccordions.push({
184+
key: `${section.category}-${modelIndex}`,
185+
header: `${section.category}: ${modelName}`,
186+
modelIndex,
187+
phaseKind: phases[0],
188+
});
189+
} else {
190+
phases.forEach((phaseKind) => {
191+
allAccordions.push({
192+
key: `${section.category}-${modelIndex}-${phaseKind}`,
193+
header: `${section.category}: ${modelName}${phaseLabel(phaseKind)}`,
194+
modelIndex,
195+
phaseKind,
196+
});
197+
});
198+
}
199+
});
200+
});
98201

99202
return (
100203
<MainContainer>
@@ -103,32 +206,23 @@ const CompareGridSimulations: React.FC<CompareGridSimulationsProps> = ({ gridIds
103206
Comparing {results.length} grid simulations. Each line shows how the selected output substance responds
104207
across the configured range.
105208
</Typography>
106-
<NativeSelect
107-
id="compareGridSubstance"
108-
label="Output substance"
109-
value={selectedSubstance}
110-
onChange={(e) => setSubstance(e.target.value)}
111-
style={{ maxWidth: "400px", marginBottom: "1rem" }}
112-
>
113-
{allSubstances.map((s) => (
114-
<option key={s} value={s}>
115-
{optionName(s)}
116-
</option>
209+
210+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
211+
{allAccordions.map((item, index) => (
212+
<Accordion key={item.key}>
213+
<Accordion.Item isExpanded={index === allAccordions.length - 1}>
214+
<Accordion.Header>{item.header}</Accordion.Header>
215+
<Accordion.Panel>
216+
<CompareSection
217+
results={results}
218+
modelIndex={item.modelIndex}
219+
phaseKind={item.phaseKind}
220+
/>
221+
</Accordion.Panel>
222+
</Accordion.Item>
223+
</Accordion>
117224
))}
118-
</NativeSelect>
119-
{selectedSubstance === "" ? (
120-
<Typography variant="body_short" italic>
121-
No output substances available to compare.
122-
</Typography>
123-
) : (
124-
<LineChart
125-
xValues={unifiedXValues}
126-
series={series}
127-
xAxisLabel={xAxisLabel}
128-
yAxisLabel={`${selectedSubstance} (ppm)`}
129-
aspectRatio={2}
130-
/>
131-
)}
225+
</div>
132226
</MainContainer>
133227
);
134228
};

0 commit comments

Comments
 (0)