Skip to content

Commit

Permalink
Merge remote-tracking branch 'equinor/main' into refactor-simulation-…
Browse files Browse the repository at this point in the history
…time-series-with-atoms
  • Loading branch information
rubenthoms committed Apr 23, 2024
2 parents 172a378 + 182dad1 commit 516268e
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export function Settings({ settingsContext, workbenchSession }: ModuleSettingsPr

const [selectedVisualizationType, setSelectedVisualizationType] =
settingsContext.useSettingsToViewInterfaceState("selectedVisualizationType");
const [showIndividualRealizationValues, setShowIndividualRealizationValues] =
settingsContext.useSettingsToViewInterfaceState("showIndividualRealizationValues");
const [showPercentilesAndMeanLines, setShowPercentilesAndMeanLines] =
settingsContext.useSettingsToViewInterfaceState("showPercentilesAndMeanLines");

function handleEnsembleSelectionChange(ensembleIdents: EnsembleIdent[]) {
setSelectedEnsembleIdents(ensembleIdents);
Expand All @@ -55,6 +59,12 @@ export function Settings({ settingsContext, workbenchSession }: ModuleSettingsPr
function handleVisualizationTypeChange(event: React.ChangeEvent<HTMLInputElement>) {
setSelectedVisualizationType(event.target.value as ParameterDistributionPlotType);
}
function handleShowIndividualRealizationValuesChange(_: React.ChangeEvent<HTMLInputElement>, checked: boolean) {
setShowIndividualRealizationValues(checked);
}
function handleShowPercentilesAndMeanLinesChange(_: React.ChangeEvent<HTMLInputElement>, checked: boolean) {
setShowPercentilesAndMeanLines(checked);
}

return (
<div className="flex flex-col gap-2">
Expand All @@ -67,6 +77,21 @@ export function Settings({ settingsContext, workbenchSession }: ModuleSettingsPr
onChange={handleVisualizationTypeChange}
/>
</CollapsibleGroup>
<CollapsibleGroup title="Plot options" expanded>
<div className="flex flex-col gap-2">
{"Show additional data"}
<Checkbox
label="Individual realization values"
checked={showIndividualRealizationValues}
onChange={handleShowIndividualRealizationValuesChange}
/>
<Checkbox
label="Markers P10, Mean, P90"
checked={showPercentilesAndMeanLines}
onChange={handleShowPercentilesAndMeanLinesChange}
/>
</div>
</CollapsibleGroup>
<CollapsibleGroup title="Ensembles" expanded>
<EnsembleSelect
ensembleSet={ensembleSet}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { ParameterDistributionPlotType } from "./typesAndEnums";
export type Interface = {
baseStates: {
selectedVisualizationType: ParameterDistributionPlotType;
showIndividualRealizationValues: boolean;
showPercentilesAndMeanLines: boolean;
};
derivedStates: {
selectedEnsembleIdents: EnsembleIdent[];
Expand All @@ -18,6 +20,8 @@ export type Interface = {
export const interfaceInitialization: InterfaceInitialization<Interface> = {
baseStates: {
selectedVisualizationType: ParameterDistributionPlotType.DISTRIBUTION_PLOT,
showIndividualRealizationValues: false,
showPercentilesAndMeanLines: false,
},
derivedStates: {
selectedEnsembleIdents: (get) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ParameterIdent } from "@framework/EnsembleParameters";

export type EnsembleParameterValues = {
export type EnsembleParameterRealizationsAndValues = {
ensembleDisplayName: string;
realizations: number[];
values: number[];
};
export type ParameterDataArr = {
parameterIdent: ParameterIdent;
ensembleParameterValues: EnsembleParameterValues[];
ensembleParameterRealizationAndValues: EnsembleParameterRealizationsAndValues[];
};

export enum ParameterDistributionPlotType {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from "react";
import Plot from "react-plotly.js";

import { computeQuantile } from "@modules_shared/statistics";

import { PlotType } from "plotly.js";

import { ParameterDataArr, ParameterDistributionPlotType } from "../../typesAndEnums";
Expand All @@ -9,71 +11,214 @@ type ParameterDistributionPlotProps = {
dataArr: ParameterDataArr[];
ensembleColors: Map<string, string>;
plotType: ParameterDistributionPlotType;
showIndividualRealizationValues: boolean;
showPercentilesAndMeanLines: boolean;
width: number;
height: number;
};

function convertToPlotlyPlotType(plotType: ParameterDistributionPlotType): PlotType {
if (plotType == ParameterDistributionPlotType.BOX_PLOT) {
return "box" as PlotType;
}
if (plotType == ParameterDistributionPlotType.DISTRIBUTION_PLOT) {
return "violin" as PlotType;
}
throw new Error(`Unknown plot type: ${plotType}`);
}

export const ParameterDistributionPlot: React.FC<ParameterDistributionPlotProps> = (props) => {
const numSubplots = props.dataArr.length;
const numColumns = Math.ceil(Math.sqrt(numSubplots));
const numRows = Math.ceil(numSubplots / numColumns);
const addedLegendNames: Set<string> = new Set();

function generateTraces(): any {
const traces: any = [];
const showRugTraces =
props.plotType == ParameterDistributionPlotType.DISTRIBUTION_PLOT && props.showIndividualRealizationValues;

function generateDistributionPlotTraces(): any[] {
const traces: any[] = [];
let subplotIndex = 1;

const convertedPlotType = convertToPlotlyPlotType(props.plotType);
const hoverInfo = props.plotType == ParameterDistributionPlotType.BOX_PLOT ? "" : "none";
props.dataArr.forEach((parameterData) => {
parameterData.ensembleParameterRealizationAndValues.forEach((ensembleData, index) => {
const shouldShowLegend = !addedLegendNames.has(ensembleData.ensembleDisplayName);
if (shouldShowLegend) {
addedLegendNames.add(ensembleData.ensembleDisplayName);
}
const ensembleColor = props.ensembleColors.get(ensembleData.ensembleDisplayName);

const distributionTrace = {
x: ensembleData.values,
type: "violin" as PlotType,
spanmode: "hard",
name: ensembleData.ensembleDisplayName,
legendgroup: ensembleData.ensembleDisplayName,
marker: { color: ensembleColor },
xaxis: `x${subplotIndex}`,
yaxis: `y${subplotIndex}`,
showlegend: shouldShowLegend,
y0: 0,
hoverinfo: "none",
meanline: { visible: true },
orientation: "h",
side: "positive",
width: 2,
points: false,
};
traces.push(distributionTrace);

if (props.showPercentilesAndMeanLines) {
const yPosition = 0;
traces.push(
...createQuantileAndMeanMarkerTraces(
ensembleData.values,
yPosition,
ensembleData.ensembleDisplayName,
ensembleColor,
subplotIndex
)
);
}

if (props.showIndividualRealizationValues) {
const hoverText = ensembleData.values.map(
(_, index) => `Realization: ${ensembleData.realizations[index]}`
);

// Distribution plot shows positive values, thus the rug plot is placed below 0.
// Align the realization values horizontally below the distribution plot
const yPosition = -0.1 - index * 0.1; // Offset -0.1, and 0.1 between each ensemble
const yValues = ensembleData.values.map(() => yPosition); // Align horizontally with same y-position

const rugTrace = {
x: ensembleData.values, // Use the same x values as your main trace
y: yValues,
type: "rug",
name: ensembleData.ensembleDisplayName,
legendgroup: ensembleData.ensembleDisplayName,
xaxis: `x${subplotIndex}`,
yaxis: `y${subplotIndex}`,
hovertext: hoverText,
hoverinfo: "x+text+name",
mode: "markers",
marker: {
color: props.ensembleColors.get(ensembleData.ensembleDisplayName),
symbol: "line-ns-open",
},
showlegend: false,
};
traces.push(rugTrace);
}
});

subplotIndex++;
});

return traces;
}

function generateBoxPlotTraces(): any[] {
const traces: any[] = [];
let subplotIndex = 1;

props.dataArr.forEach((parameterData) => {
parameterData.ensembleParameterValues.forEach((ensembleValue, index) => {
const shouldShowLegend = !addedLegendNames.has(ensembleValue.ensembleDisplayName);
parameterData.ensembleParameterRealizationAndValues.forEach((ensembleData, index) => {
const shouldShowLegend = !addedLegendNames.has(ensembleData.ensembleDisplayName);
if (shouldShowLegend) {
addedLegendNames.add(ensembleValue.ensembleDisplayName);
addedLegendNames.add(ensembleData.ensembleDisplayName);
}

let verticalPosition = 0;
if (props.plotType == ParameterDistributionPlotType.BOX_PLOT) {
verticalPosition = index * (2 + 1); // 2 is the height of each box + 1 space
if (ensembleData.values.length !== ensembleData.realizations.length) {
throw new Error("Realizations and values must have the same length");
}

const ensembleColor = props.ensembleColors.get(ensembleData.ensembleDisplayName);

const verticalPosition = index * (2 + 1); // 2 is the height of each box + 1 space
const hoverText = ensembleData.values.map(
(_, index) => `Realization: ${ensembleData.realizations[index]}`
);

const trace = {
x: ensembleValue.values,
type: convertedPlotType,
name: ensembleValue.ensembleDisplayName,
legendgroup: ensembleValue.ensembleDisplayName,
marker: { color: props.ensembleColors.get(ensembleValue.ensembleDisplayName) },
x: ensembleData.values,
type: "box",
name: ensembleData.ensembleDisplayName,
legendgroup: ensembleData.ensembleDisplayName,
marker: { color: ensembleColor },
xaxis: `x${subplotIndex}`,
yaxis: `y${subplotIndex}`,
showlegend: shouldShowLegend,
y0: verticalPosition,
hoverinfo: hoverInfo,
hoverinfo: "x+text+name",
hovertext: hoverText,
meanline_visible: true,
orientation: "h",
side: "positive",
width: 2,
points: false,
boxpoints: props.showIndividualRealizationValues ? "all" : "outliers",
};
traces.push(trace);

if (props.showPercentilesAndMeanLines) {
traces.push(
...createQuantileAndMeanMarkerTraces(
ensembleData.values,
verticalPosition,
ensembleData.ensembleDisplayName,
ensembleColor,
subplotIndex
)
);
}
});
subplotIndex++;
});

return traces;
}

function createQuantileAndMeanMarkerTraces(
parameterValues: number[],
yPosition: number,
ensembleName: string,
ensembleColor: string | undefined,
subplotIndex: number
): any[] {
const p90 = computeQuantile(parameterValues, 0.9);
const p10 = computeQuantile(parameterValues, 0.1);
const mean = parameterValues.reduce((a, b) => a + b, 0) / parameterValues.length;
const p10Trace = {
x: [p10],
y: [yPosition],
type: "scatter",
hoverinfo: "x+text",
hovertext: "P10",
showlegend: false,
legendgroup: ensembleName,
xaxis: `x${subplotIndex}`,
yaxis: `y${subplotIndex}`,
marker: { color: ensembleColor, symbol: "x", size: 10 },
};
const meanTrace = {
x: [mean],
y: [yPosition],
type: "scatter",
hoverinfo: "x+text",
hovertext: "Mean",
showlegend: false,
legendgroup: ensembleName,
xaxis: `x${subplotIndex}`,
yaxis: `y${subplotIndex}`,
marker: { color: ensembleColor, symbol: "x", size: 10 },
};
const p90Trace = {
x: [p90],
y: [yPosition],
type: "scatter",
hoverinfo: "x+text",
hovertext: "P90",
showlegend: false,
legendgroup: ensembleName,
xaxis: `x${subplotIndex}`,
yaxis: `y${subplotIndex}`,
marker: { color: ensembleColor, symbol: "x", size: 10 },
};

return [p10Trace, meanTrace, p90Trace];
}

function generateLayout(): any {
const layout: any = {
height: props.height,
Expand All @@ -89,18 +234,21 @@ export const ParameterDistributionPlot: React.FC<ParameterDistributionPlotProps>
title: props.dataArr[i - 1].parameterIdent,
mirror: true,
showline: true,
zeroline: false,
linewidth: 1,
linecolor: "black",
};

layout[`yaxis${i}`] = {
showticklabels: false,
showgrid: false,
zeroline: false,
zeroline: showRugTraces,
mirror: true,
showline: true,
linewidth: 1,
linecolor: "black",
};

layout.annotations.push({
text: props.dataArr[i - 1].parameterIdent.name,
showarrow: false,
Expand All @@ -114,7 +262,14 @@ export const ParameterDistributionPlot: React.FC<ParameterDistributionPlotProps>
return layout;
}

const data = generateTraces();
let data = [];
if (props.plotType == ParameterDistributionPlotType.DISTRIBUTION_PLOT) {
data = generateDistributionPlotTraces();
}
if (props.plotType == ParameterDistributionPlotType.BOX_PLOT) {
data = generateBoxPlotTraces();
}

const layout = generateLayout();

return <Plot data={data} layout={layout} config={{ displayModeBar: false }} />;
Expand Down
Loading

0 comments on commit 516268e

Please sign in to comment.