Skip to content

Commit

Permalink
Add color by parameter for SimulationTimeSeriesMatrix (#320)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgenherje authored Sep 26, 2023
1 parent 461612a commit fd1d428
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 173 deletions.
2 changes: 1 addition & 1 deletion frontend/src/framework/Ensemble.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EnsembleIdent } from "./EnsembleIdent";
import { EnsembleSensitivities, Sensitivity } from "./EnsembleSensitivities";
import { EnsembleParameters, Parameter } from "./EnsembleParameters";
import { EnsembleSensitivities, Sensitivity } from "./EnsembleSensitivities";

export class Ensemble {
private _ensembleIdent: EnsembleIdent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { view } from "./view";

const defaultState: State = {
groupBy: GroupBy.TIME_SERIES,
colorRealizationsByParameter: false,
parameterIdent: null,
visualizationMode: VisualizationMode.INDIVIDUAL_REALIZATIONS,
vectorSpecifications: [],
resamplingFrequency: Frequency_api.MONTHLY,
Expand Down
115 changes: 97 additions & 18 deletions frontend/src/modules/SimulationTimeSeriesMatrix/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";

import { Frequency_api, StatisticFunction_api, VectorDescription_api } from "@api";
import { Frequency_api, StatisticFunction_api } from "@api";
import { EnsembleIdent } from "@framework/EnsembleIdent";
import { ParameterIdent, ParameterType } from "@framework/EnsembleParameters";
import { EnsembleSet } from "@framework/EnsembleSet";
import { ModuleFCProps } from "@framework/Module";
import { useEnsembleSet } from "@framework/WorkbenchSession";
Expand All @@ -16,6 +17,7 @@ import { Label } from "@lib/components/Label";
import { RadioGroup } from "@lib/components/RadioGroup";
import { SmartNodeSelectorSelection, TreeDataNode } from "@lib/components/SmartNodeSelector";
import { VectorSelector } from "@lib/components/VectorSelector";
import { useValidState } from "@lib/hooks/useValidState";
import { resolveClassNames } from "@lib/utils/resolveClassNames";
import { createVectorSelectorDataFromVectors } from "@lib/utils/vectorSelectorUtils";

Expand All @@ -38,35 +40,28 @@ import { EnsembleVectorListsHelper } from "./utils/ensemblesVectorListHelper";

export function settings({ moduleContext, workbenchSession }: ModuleFCProps<State>) {
const ensembleSet = useEnsembleSet(workbenchSession);

// Store state/values
const [resampleFrequency, setResamplingFrequency] = moduleContext.useStoreState("resamplingFrequency");
const [groupBy, setGroupBy] = moduleContext.useStoreState("groupBy");
const [colorRealizationsByParameter, setColorRealizationsByParameter] =
moduleContext.useStoreState("colorRealizationsByParameter");
const [visualizationMode, setVisualizationMode] = moduleContext.useStoreState("visualizationMode");
const [showHistorical, setShowHistorical] = moduleContext.useStoreState("showHistorical");
const [showObservations, setShowObservations] = moduleContext.useStoreState("showObservations");
const [statisticsSelection, setStatisticsSelection] = moduleContext.useStoreState("statisticsSelection");
const setParameterIdent = moduleContext.useSetStoreValue("parameterIdent");
const setVectorSpecifications = moduleContext.useSetStoreValue("vectorSpecifications");

// States
const [previousEnsembleSet, setPreviousEnsembleSet] = React.useState<EnsembleSet>(ensembleSet);
const [selectedEnsembleIdents, setSelectedEnsembleIdents] = React.useState<EnsembleIdent[]>([]);
const [selectedVectorNames, setSelectedVectorNames] = React.useState<string[]>([]);
const [vectorSelectorData, setVectorSelectorData] = React.useState<TreeDataNode[]>([]);

const [prevVisualizationMode, setPrevVisualizationMode] = React.useState<VisualizationMode>(visualizationMode);
if (prevVisualizationMode !== visualizationMode) {
setPrevVisualizationMode(visualizationMode);
}

const vectorListQueries = useVectorListQueries(selectedEnsembleIdents);
const ensembleVectorListsHelper = new EnsembleVectorListsHelper(selectedEnsembleIdents, vectorListQueries);
const vectorsUnion: VectorDescription_api[] = ensembleVectorListsHelper.vectorsUnion();

const selectedVectorNamesHasHistorical = ensembleVectorListsHelper.hasAnyHistoricalVector(selectedVectorNames);
const currentVectorSelectorData = createVectorSelectorDataFromVectors(vectorsUnion.map((vector) => vector.name));

// Only update if all vector lists are retrieved before updating vectorSelectorData has changed
const hasVectorListQueriesErrorOrLoading = vectorListQueries.some((query) => query.isLoading || query.isError);
if (!hasVectorListQueriesErrorOrLoading && !isEqual(currentVectorSelectorData, vectorSelectorData)) {
setVectorSelectorData(currentVectorSelectorData);
if (visualizationMode !== prevVisualizationMode) {
setPrevVisualizationMode(visualizationMode);
}

if (!isEqual(ensembleSet, previousEnsembleSet)) {
Expand All @@ -81,6 +76,35 @@ export function settings({ moduleContext, workbenchSession }: ModuleFCProps<Stat
setPreviousEnsembleSet(ensembleSet);
}

const vectorListQueries = useVectorListQueries(selectedEnsembleIdents);
const ensembleVectorListsHelper = new EnsembleVectorListsHelper(selectedEnsembleIdents, vectorListQueries);
const selectedVectorNamesHasHistorical = ensembleVectorListsHelper.hasAnyHistoricalVector(selectedVectorNames);
const currentVectorSelectorData = createVectorSelectorDataFromVectors(ensembleVectorListsHelper.vectorsUnion());

// Get union of continuous and non-constant parameters for selected ensembles and set valid parameter ident str
const continuousAndNonConstantParametersUnion: ParameterIdent[] = [];
for (const ensembleIdent of selectedEnsembleIdents) {
const ensemble = ensembleSet.findEnsemble(ensembleIdent);
if (ensemble === null) continue;

for (const parameter of ensemble.getParameters().getParameterIdents(ParameterType.CONTINUOUS)) {
if (continuousAndNonConstantParametersUnion.some((param) => param.equals(parameter))) continue;
if (ensemble.getParameters().getParameter(parameter).isConstant) continue;

continuousAndNonConstantParametersUnion.push(parameter);
}
}
const [selectedParameterIdentStr, setSelectedParameterIdentStr] = useValidState<string | null>(null, [
continuousAndNonConstantParametersUnion,
(item: ParameterIdent) => item.toString(),
]);

// Await update of vectorSelectorData until all vector lists are retrieved
const hasVectorListQueriesErrorOrFetching = vectorListQueries.some((query) => query.isFetching || query.isError);
if (!hasVectorListQueriesErrorOrFetching && !isEqual(currentVectorSelectorData, vectorSelectorData)) {
setVectorSelectorData(currentVectorSelectorData);
}

React.useEffect(
function propagateVectorSpecsToView() {
const newVectorSpecifications: VectorSpec[] = [];
Expand All @@ -97,16 +121,44 @@ export function settings({ moduleContext, workbenchSession }: ModuleFCProps<Stat
});
}
}

setVectorSpecifications(newVectorSpecifications);
},
[selectedEnsembleIdents, selectedVectorNames, ensembleVectorListsHelper.numberOfQueriesWithData()]
);

React.useEffect(
function propagateParameterIdentToView() {
if (selectedParameterIdentStr === null) {
setParameterIdent(null);
return;
}

// Try/catch as ParameterIdent.fromString() can throw
try {
const newParameterIdent = ParameterIdent.fromString(selectedParameterIdentStr);
const isParameterInUnion = continuousAndNonConstantParametersUnion.some((parameter) =>
parameter.equals(newParameterIdent)
);
if (isParameterInUnion) {
setParameterIdent(newParameterIdent);
} else {
setParameterIdent(null);
}
} catch {
setParameterIdent(null);
}
},
[selectedParameterIdentStr]
);

function handleGroupByChange(event: React.ChangeEvent<HTMLInputElement>) {
setGroupBy(event.target.value as GroupBy);
}

function handleColorByParameterChange(parameterIdentStr: string) {
setSelectedParameterIdentStr(parameterIdentStr);
}

function handleEnsembleSelectChange(ensembleIdentArr: EnsembleIdent[]) {
setSelectedEnsembleIdents(ensembleIdentArr);
}
Expand Down Expand Up @@ -234,7 +286,6 @@ export function settings({ moduleContext, workbenchSession }: ModuleFCProps<Stat
>
<VectorSelector
data={vectorSelectorData}
selectedTags={selectedVectorNames}
placeholder="Add new vector..."
maxNumSelectedNodes={50}
numSecondsUntilSuggestionsAreShown={0.5}
Expand All @@ -244,6 +295,34 @@ export function settings({ moduleContext, workbenchSession }: ModuleFCProps<Stat
</ApiStatesWrapper>
</div>
</CollapsibleGroup>
<CollapsibleGroup expanded={true} title="Color realization by parameter">
<Checkbox
label="Enable"
checked={colorRealizationsByParameter}
disabled={visualizationMode !== VisualizationMode.INDIVIDUAL_REALIZATIONS}
onChange={(event) => {
setColorRealizationsByParameter(event.target.checked);
}}
/>
<div
className={resolveClassNames("mt-4 ml-6 mb-4", {
["pointer-events-none opacity-70"]:
!colorRealizationsByParameter ||
visualizationMode !== VisualizationMode.INDIVIDUAL_REALIZATIONS,
})}
>
<Dropdown
options={continuousAndNonConstantParametersUnion.map((elm) => {
return {
value: elm.toString(),
label: elm.groupName ? `${elm.groupName}:${elm.name}` : elm.name,
};
})}
value={selectedParameterIdentStr?.toString() ?? undefined}
onChange={handleColorByParameterChange}
/>
</div>
</CollapsibleGroup>
<CollapsibleGroup expanded={true} title="Visualization">
<RadioGroup
value={visualizationMode}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/modules/SimulationTimeSeriesMatrix/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Frequency_api, StatisticFunction_api } from "@api";
import { EnsembleIdent } from "@framework/EnsembleIdent";
import { ParameterIdent } from "@framework/EnsembleParameters";

export interface VectorSpec {
ensembleIdent: EnsembleIdent;
Expand All @@ -21,18 +22,15 @@ export const VisualizationModeEnumToStringMapping = {
[VisualizationMode.STATISTICS_AND_REALIZATIONS]: "Statistics + Realizations",
};

// NOTE: Add None as option?
export enum GroupBy {
ENSEMBLE = "ensemble",
TIME_SERIES = "timeSeries",
}

// NOTE: Add None as option?
export const GroupByEnumToStringMapping = {
[GroupBy.ENSEMBLE]: "Ensemble",
[GroupBy.TIME_SERIES]: "Time Series",
};

export const StatisticFunctionEnumToStringMapping = {
[StatisticFunction_api.MEAN]: "Mean",
[StatisticFunction_api.MIN]: "Min",
Expand Down Expand Up @@ -64,6 +62,8 @@ export const FrequencyEnumToStringMapping = {

export interface State {
groupBy: GroupBy;
colorRealizationsByParameter: boolean;
parameterIdent: ParameterIdent | null;
visualizationMode: VisualizationMode;
vectorSpecifications: VectorSpec[] | null;
resamplingFrequency: Frequency_api | null;
Expand Down
Loading

0 comments on commit fd1d428

Please sign in to comment.