diff --git a/web/src/api/asset_interfaces/metric/charts/annotationInterfaces.ts b/web/src/api/asset_interfaces/metric/charts/annotationInterfaces.ts index 107e855b4..237e33651 100644 --- a/web/src/api/asset_interfaces/metric/charts/annotationInterfaces.ts +++ b/web/src/api/asset_interfaces/metric/charts/annotationInterfaces.ts @@ -22,4 +22,5 @@ export interface Trendline { | 'median'; //default is linear trend trendLineColor?: string | null; //OPTIONAL: default is #000000 columnId: string; + id?: string; } diff --git a/web/src/components/ui/charts/BusterChartJS/hooks/useTrendlines/useTrendlines.test.ts b/web/src/components/ui/charts/BusterChartJS/hooks/useTrendlines/useTrendlines.test.ts index 7ce529b41..1a9dc00be 100644 --- a/web/src/components/ui/charts/BusterChartJS/hooks/useTrendlines/useTrendlines.test.ts +++ b/web/src/components/ui/charts/BusterChartJS/hooks/useTrendlines/useTrendlines.test.ts @@ -24,7 +24,8 @@ describe('useTrendlines', () => { trendlines: [] as TrendlineDataset[], columnLabelFormats: {} as Record, selectedChartType: 'line' as ChartType, - lineGroupType: null + lineGroupType: null, + barGroupType: null }; it('returns the expected structure', () => { @@ -153,6 +154,7 @@ describe('useTrendlines', () => { const columnId = 'col1'; const props = { ...defaultProps, + trendlines: [ mockTrendlineDataset({ id: 'test-linear-slope', diff --git a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/trendlineDatasetCreator.ts b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/trendlineDatasetCreator.ts index 4714a5b6d..37d82b3f9 100644 --- a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/trendlineDatasetCreator.ts +++ b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/trendlineDatasetCreator.ts @@ -282,14 +282,14 @@ export const trendlineDatasetCreator: Record< }, average: (trendline, datasetsWithTicks) => { - const datasets = datasetsWithTicks.datasets; - const selectedDataset = datasets.find((dataset) => dataset.id === trendline.columnId); + const { validData, ticks, xAxisColumn, selectedDatasets } = getValidDataAndTicks( + datasetsWithTicks.datasets, + trendline + ); - if (!selectedDataset?.data || selectedDataset.data.length === 0) return []; - - // Filter out null/undefined values - const validData = selectedDataset.data.filter((value) => value !== null && value !== undefined); + if (!selectedDatasets || selectedDatasets.length === 0 || validData.length === 0) return []; + // Sum all valid values and divide by the count if (validData.length === 0) return []; // Sum all valid values and divide by the count @@ -313,14 +313,12 @@ export const trendlineDatasetCreator: Record< }, min: (trendline, datasetsWithTicks) => { - const datasets = datasetsWithTicks.datasets; - const selectedDataset = datasets.find((dataset) => dataset.id === trendline.columnId); + const { validData, ticks, xAxisColumn, selectedDatasets } = getValidDataAndTicks( + datasetsWithTicks.datasets, + trendline + ); - if (!selectedDataset?.data || selectedDataset.data.length === 0) return []; - - // Filter out null/undefined values - const validData = selectedDataset.data.filter((value) => value !== null && value !== undefined); - if (validData.length === 0) return []; + if (!selectedDatasets || selectedDatasets.length === 0 || validData.length === 0) return []; // Use the first valid value as initial accumulator const min = validData.reduce((acc, datapoint) => { @@ -341,14 +339,12 @@ export const trendlineDatasetCreator: Record< }, max: (trendline, datasetsWithTicks) => { - const datasets = datasetsWithTicks.datasets; - const selectedDataset = datasets.find((dataset) => dataset.id === trendline.columnId); + const { validData, ticks, xAxisColumn, selectedDatasets } = getValidDataAndTicks( + datasetsWithTicks.datasets, + trendline + ); - if (!selectedDataset?.data || selectedDataset.data.length === 0) return []; - - // Filter out null/undefined values - const validData = selectedDataset.data.filter((value) => value !== null && value !== undefined); - if (validData.length === 0) return []; + if (!selectedDatasets || selectedDatasets.length === 0 || validData.length === 0) return []; // Use the first valid value as initial accumulator const max = validData.reduce((acc, datapoint) => { @@ -369,15 +365,15 @@ export const trendlineDatasetCreator: Record< }, median: (trendline, datasetsWithTicks) => { - const datasets = datasetsWithTicks.datasets; - const selectedDataset = datasets.find((dataset) => dataset.id === trendline.columnId); + const { validData, ticks, xAxisColumn, selectedDatasets } = getValidDataAndTicks( + datasetsWithTicks.datasets, + trendline + ); - if (!selectedDataset?.data || selectedDataset.data.length === 0) return []; + if (!selectedDatasets || selectedDatasets.length === 0 || validData.length === 0) return []; // Sort the data and get the middle value - const sortedData = [...selectedDataset.data] - .filter((value) => value !== null && value !== undefined) - .sort((a, b) => (a as number) - (b as number)); + const sortedData = [...validData].sort((a, b) => (a as number) - (b as number)); let median: number; const midIndex = Math.floor(sortedData.length / 2); diff --git a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/useDataTrendlineOptions.ts b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/useDataTrendlineOptions.ts index 842a62372..f843ecb90 100644 --- a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/useDataTrendlineOptions.ts +++ b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDataTrendlineOptions/useDataTrendlineOptions.ts @@ -42,8 +42,9 @@ export const useDataTrendlineOptions = ({ !hasTrendlines || !datasetOptions || !datasetOptions.datasets.length - ) + ) { return [] as TrendlineDataset[]; + } const trendlineDatasets: TrendlineDataset[] = []; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditTrendline/EditTrendline.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditTrendline/EditTrendline.tsx index 10228805b..90be88ff0 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditTrendline/EditTrendline.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditTrendline/EditTrendline.tsx @@ -18,6 +18,7 @@ import { EditTrendlineShowLine } from './EditTrendlineShowLine'; import { EditTrendlineOption } from './EditTrendlineOption'; import { TypeToLabel } from './config'; import { JOIN_CHARACTER } from '@/components/ui/charts/commonHelpers'; +import isEqual from 'lodash/isEqual'; export interface LoopTrendline extends Trendline { id: string; @@ -40,7 +41,7 @@ export const EditTrendline: React.FC<{ selectedChartType }) => { const [trends, _setTrends] = useState( - trendlines.map((trend) => ({ ...trend, id: uuidv4() })) + trendlines.map((trend) => ({ ...trend, id: trend.id || uuidv4() })) ); const [newTrendIds, { add: addNewTrendId }] = useSet(); @@ -60,13 +61,30 @@ export const EditTrendline: React.FC<{ }, []); const onAddTrendline = useMemoizedFn(() => { + const getNewType = () => { + const types = [ + 'linear_regression', + 'polynomial_regression', + 'exponential_regression', + 'logarithmic_regression', + 'average', + 'min', + 'max', + 'median' + ] as const; + return types[Math.floor(Math.random() * types.length)]; + }; + + const hasLinearRegression = trends.some((trend) => trend.type === 'linear_regression'); + const type = hasLinearRegression ? getNewType() : ('linear_regression' as const); + const newTrendline: Required = { id: uuidv4(), show: true, showTrendlineLabel: false, trendlineLabel: null, - type: 'linear_regression', - trendLineColor: null, + type, + trendLineColor: '#FF0000', columnId: selectedAxis.y[0] || '' }; @@ -77,12 +95,8 @@ export const EditTrendline: React.FC<{ }); const onUpdateTrendlines = useMemoizedFn((trends: LoopTrendline[]) => { - const newTrends = trends.map(({ id, ...rest }) => ({ - ...rest - })); - setTimeout(() => { - onUpdateChartConfig({ trendlines: newTrends }); + onUpdateChartConfig({ trendlines: trends }); }, 30); }); @@ -120,7 +134,13 @@ export const EditTrendline: React.FC<{ }; }, [trends]); - //TODO: fix the bug where we need to "reset" the trends when the reset button is clicked + useEffect(() => { + const updatedTrends = trendlines.map((trend) => ({ ...trend, id: trend.id || uuidv4() })); + + if (!isEqual(updatedTrends, trends)) { + _setTrends(updatedTrends); + } + }, [trendlines]); return (