Skip to content

Commit

Permalink
Curve custom range (#2624)
Browse files Browse the repository at this point in the history
  • Loading branch information
robertbasti authored Dec 18, 2024
1 parent a86886f commit 5339e5e
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ExportableContentTableColumn } from "components/ContentViews/table";
import { CurveSpecification } from "models/logData";
import { CustomCurveRange } from "./CurveValuesPlot";

const calculateMean = (arr: number[]): number =>
arr.reduce((a, b) => a + b, 0) / arr.length;
Expand Down Expand Up @@ -113,7 +114,9 @@ export const transformCurveData = (
data: any[],
columns: ExportableContentTableColumn<CurveSpecification>[],
thresholdLevel: ThresholdLevel,
removeOutliers: boolean
removeOutliers: boolean,
ranges: CustomCurveRange[],
applyCustomRanges: boolean
) => {
let transformedData = data;

Expand All @@ -122,5 +125,20 @@ export const transformCurveData = (
}
// Other potential transformations should be added here.

if (applyCustomRanges === true) {
const dataWithRange = transformedData.map((dataRow) => ({ ...dataRow }));
ranges.forEach((range) => {
for (let i = 0; i < dataWithRange.length; i++) {
if (
dataWithRange[i][range.curve] < range.minValue ||
dataWithRange[i][range.curve] > range.maxValue
) {
delete dataWithRange[i][range.curve];
}
}
});
return dataWithRange;
}

return transformedData;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { EdsProvider, Switch, Typography } from "@equinor/eds-core-react";
import {
Button,
EdsProvider,
Switch,
Typography
} from "@equinor/eds-core-react";
import {
ThresholdLevel,
transformCurveData
Expand Down Expand Up @@ -32,6 +37,8 @@ import { useParams } from "react-router-dom";
import { RouterLogType } from "routes/routerConstants";
import { Colors } from "styles/Colors";
import { normaliseThemeForEds } from "../../tools/themeHelpers.ts";
import { SettingCustomRanges } from "./SettingCustomRanges.tsx";
import { Box } from "@mui/material";

const COLUMN_WIDTH = 135;
const MNEMONIC_LABEL_WIDTH = COLUMN_WIDTH - 10;
Expand All @@ -44,6 +51,11 @@ interface ControlledTooltipProps {
content: string;
}

export interface CustomCurveRange {
curve: string;
minValue: number;
maxValue: number;
}
interface CurveValuesPlotProps {
data: any[];
columns: ExportableContentTableColumn<CurveSpecification>[];
Expand All @@ -69,17 +81,24 @@ export const CurveValuesPlot = React.memo(
} = useOperationState();
const [enableScatter, setEnableScatter] = useState<boolean>(false);
const [removeOutliers, setRemoveOutliers] = useState<boolean>(false);
const [useCustomRanges, setUseCustomRanges] = useState<boolean>(false);
const [refreshGraph, setRefreshGraph] = useState<boolean>(false);
const [outliersThresholdLevel, setOutliersThresholdLevel] =
useState<ThresholdLevel>(ThresholdLevel.Medium);
const chart = useRef<ECharts>(null);
const selectedLabels = useRef<Record<string, boolean>>(null);
const scrollIndex = useRef<number>(0);
const horizontalZoom = useRef<[number, number]>([0, 100]);

const verticalZoom = useRef<[number, number]>([0, 100]);
const [maxColumns, setMaxColumns] = useState<number>(15);

const { width: contentViewWidth } = useContext(
ContentViewDimensionsContext
);

const [defineCustomRanges, setDefineCustomRanges] =
useState<boolean>(false);
const { logType } = useParams();
const isTimeLog = logType === RouterLogType.TIME;
const extraWidth = getExtraWidth(data, columns, dateTimeFormat, isTimeLog);
Expand All @@ -89,15 +108,53 @@ export const CurveValuesPlot = React.memo(
useState<ControlledTooltipProps>({
visible: false
} as ControlledTooltipProps);

const minMaxValuesCalculation = (
myColumns: ExportableContentTableColumn<CurveSpecification>[]
) =>
myColumns
.map((col) => col.columnOf.mnemonic)
.map((curve) => {
const curveData = props.data
.map((obj) => obj[curve])
.filter(Number.isFinite);
return {
curve: curve,
minValue:
curveData.length == 0
? null
: curveData.reduce((min, v) => (min <= v ? min : v), Infinity),
maxValue:
curveData.length == 0
? null
: curveData.reduce((max, v) => (max >= v ? max : v), -Infinity)
};
})
.slice(1);

const [ranges, setRanges] = useState<CustomCurveRange[]>(
minMaxValuesCalculation(columns)
);

const transformedData = useMemo(
() =>
transformCurveData(
data,
columns,
outliersThresholdLevel,
!autoRefresh && removeOutliers
!autoRefresh && removeOutliers,
ranges,
useCustomRanges
),
[data, columns, outliersThresholdLevel, removeOutliers, autoRefresh]
[
data,
columns,
outliersThresholdLevel,
removeOutliers,
autoRefresh,
ranges,
useCustomRanges
]
);

useEffect(() => {
Expand Down Expand Up @@ -125,7 +182,8 @@ export const CurveValuesPlot = React.memo(
horizontalZoom.current,
verticalZoom.current,
isTimeLog,
enableScatter
enableScatter,
refreshGraph
);

const onMouseOver = (e: any) => {
Expand Down Expand Up @@ -190,6 +248,10 @@ export const CurveValuesPlot = React.memo(
};
};

const openCustomRanges = () => {
setDefineCustomRanges(true);
};

const onLegendScroll = (params: { scrollDataIndex: number }) => {
scrollIndex.current = params.scrollDataIndex;
};
Expand Down Expand Up @@ -217,6 +279,15 @@ export const CurveValuesPlot = React.memo(
mouseout: onMouseOut
};

const onChange = (curveRanges: CustomCurveRange[]) => {
setRanges(curveRanges);
setRefreshGraph(!refreshGraph);
};

const onClose = () => {
setDefineCustomRanges(false);
};

return (
<div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
<CommonPanelContainer>
Expand Down Expand Up @@ -268,6 +339,39 @@ export const CurveValuesPlot = React.memo(
</StyledNativeSelect>
</>
)}
<Switch
checked={useCustomRanges}
onChange={() => setUseCustomRanges(!useCustomRanges)}
size={theme === UserTheme.Compact ? "small" : "default"}
/>
<Typography
style={{ minWidth: "max-content", marginRight: "12px" }}
>
Show Custom Ranges
</Typography>
{useCustomRanges && (
<Button onClick={openCustomRanges}>
Define Custom Ranges
</Button>
)}
{defineCustomRanges ? (
<Box
sx={{
zIndex: 10,
position: "absolute",
width: "inherit",
top: "6.3rem",
minWidth: "174px",
pr: "0.1em"
}}
>
<SettingCustomRanges
minMaxValuesCalculation={ranges}
onChange={onChange}
onClose={onClose}
/>
</Box>
) : null}
</>
)}
</EdsProvider>
Expand Down Expand Up @@ -332,13 +436,15 @@ const getChartOption = (
horizontalZoom: [number, number],
verticalZoom: [number, number],
isTimeLog: boolean,
enableScatter: boolean
enableScatter: boolean,
_refreshGraph: boolean
) => {
_refreshGraph = true;
const VALUE_OFFSET_FROM_COLUMN = 0.01;
const AUTO_REFRESH_SIZE = 300;
const LABEL_MAXIMUM_LENGHT = 13;
const LABEL_NUMBER_MAX_LENGTH = 9;
if (autoRefresh) data = data.slice(-AUTO_REFRESH_SIZE); // Slice to avoid lag while streaming
if (autoRefresh && _refreshGraph) data = data.slice(-AUTO_REFRESH_SIZE); // Slice to avoid lag while streaming
const indexCurve = columns[0].columnOf.mnemonic;
const indexUnit = columns[0].columnOf.unit;
const dataColumns = columns.filter((col) => col.property != indexCurve);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {
Button,
CellProps,
Divider,
EdsProvider,
Table,
TextField
} from "@equinor/eds-core-react";
import { useOperationState } from "hooks/useOperationState";
import { ChangeEvent, useState } from "react";
import styled from "styled-components";
import { Colors } from "styles/Colors";
import { CustomCurveRange } from "./CurveValuesPlot";

export const SettingCustomRanges = (props: {
minMaxValuesCalculation: CustomCurveRange[];
onChange: (curveRanges: CustomCurveRange[]) => void;
onClose: () => void;
}): React.ReactElement => {
const {
operationState: { colors }
} = useOperationState();

const [ranges, setRanges] = useState<CustomCurveRange[]>(
props.minMaxValuesCalculation
);

const close = () => {
props.onClose();
};

return (
<EdsProvider density="compact">
<Container colors={colors}>
<InnerContainer>
<CloseButton onClick={close}>
Close custom ranges definition
</CloseButton>
</InnerContainer>
<Divider />
<InnerContainer>
<Table style={{ width: "100%" }}>
<Table.Head>
<Table.Row>
<StyledTableHeadCell colors={colors}>Curve</StyledTableHeadCell>
<StyledTableHeadCell colors={colors}>
Min. value
</StyledTableHeadCell>
<StyledTableHeadCell colors={colors}>
Max. value
</StyledTableHeadCell>
</Table.Row>
</Table.Head>
<Table.Body>
{(ranges ?? []).map((customRange: CustomCurveRange) => (
<Table.Row id={customRange.curve} key={customRange.curve}>
<StyledTableCell colors={colors}>
{customRange.curve}
</StyledTableCell>
<StyledTableCell colors={colors}>
<StyledTextField
id="startIndex"
defaultValue={customRange.minValue}
type="number"
colors={colors}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const range = ranges.find(
(x) => x.curve === customRange.curve
);
range.minValue = Number(e.target.value);
setRanges(ranges);
props.onChange(ranges);
}}
/>
</StyledTableCell>
<StyledTableCell colors={colors}>
<StyledTextField
id="endIndex"
defaultValue={customRange.maxValue}
type="number"
colors={colors}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
const range = ranges.find(
(x) => x.curve === customRange.curve
);
range.maxValue = Number(e.target.value);
setRanges(ranges);
props.onChange(ranges);
}}
/>
</StyledTableCell>
</Table.Row>
))}
</Table.Body>
</Table>
</InnerContainer>
</Container>
</EdsProvider>
);
};

export const StyledTableCell = styled(Table.Cell)<{ colors: Colors }>`
background-color: ${(props) =>
props.colors.interactive.tableHeaderFillResting};
color: ${(props) => props.colors.text.staticIconsDefault};
`;

const Container = styled.div<{ colors: Colors }>`
display: flex;
flex-direction: column;
gap: 0.5em;
padding: 0.5em;
user-select: none;
box-shadow: 1px 4px 5px 0px rgba(0, 0, 0, 0.3);
background: ${(props) => props.colors.ui.backgroundLight};
`;

const InnerContainer = styled.div`
display: flex;
flex-direction: column;
`;

const StyledTextField = styled(TextField)<{ colors: Colors }>`
label {
color: red;
}
div {
background-color: ${(props) => props.colors.ui.backgroundLight};
}
`;

const CloseButton = styled(Button)`
width: 300px;
`;

const StyledTableHeadCell = styled(Table.Cell)<{ colors: Colors } & CellProps>`
{
background-color: ${(props) =>
props.colors.interactive.tableHeaderFillResting};
color: ${(props) => props.colors.text.staticIconsDefault};
}
`;

0 comments on commit 5339e5e

Please sign in to comment.