Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Curve custom range #2624

Merged
merged 13 commits into from
Dec 18, 2024
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;
@@ -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;

@@ -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
@@ -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;
@@ -44,6 +51,11 @@ interface ControlledTooltipProps {
content: string;
}

export interface CustomCurveRange {
curve: string;
minValue: number;
maxValue: number;
}
interface CurveValuesPlotProps {
data: any[];
columns: ExportableContentTableColumn<CurveSpecification>[];
@@ -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);
@@ -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(() => {
@@ -125,7 +182,8 @@ export const CurveValuesPlot = React.memo(
horizontalZoom.current,
verticalZoom.current,
isTimeLog,
enableScatter
enableScatter,
refreshGraph
);

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

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

const onLegendScroll = (params: { scrollDataIndex: number }) => {
scrollIndex.current = params.scrollDataIndex;
};
@@ -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>
@@ -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>
vaclavbasniar marked this conversation as resolved.
Show resolved Hide resolved
{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>
@@ -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);
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};
}
`;