[Dashboards] use select option colors when grouping by SELECT/MULTI_SELECT fields (#16973)

closes https://github.com/twentyhq/core-team-issues/issues/2031


https://github.com/user-attachments/assets/16fbfefd-107c-45a3-979c-1f9adbb9d912
This commit is contained in:
nitin
2026-01-08 23:06:56 +05:30
committed by GitHub
parent a5eae50c66
commit aa574bacea
37 changed files with 599 additions and 55 deletions
@@ -76,6 +76,17 @@ const meta: Meta<typeof GraphWidgetBarChart> = {
seriesLabels: {
control: 'object',
},
colorMode: {
control: 'select',
options: [
'automaticPalette',
'explicitSingleColor',
'selectFieldOptionColors',
],
},
},
args: {
colorMode: 'automaticPalette',
},
};
@@ -146,6 +157,7 @@ export const Default: Story = {
yAxisLabel={args.yAxisLabel}
id={args.id}
groupMode={args.groupMode}
colorMode={args.colorMode}
/>
</Container>
),
@@ -211,6 +223,7 @@ export const Revenue: Story = {
yAxisLabel={args.yAxisLabel}
id={args.id}
groupMode={args.groupMode}
colorMode={args.colorMode}
/>
</Container>
),
@@ -275,6 +288,7 @@ export const Grouped: Story = {
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -312,6 +326,7 @@ export const Horizontal: Story = {
yAxisLabel={args.yAxisLabel}
suffix={args.suffix}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -362,6 +377,7 @@ export const WithValues: Story = {
yAxisLabel={args.yAxisLabel}
id={args.id}
groupMode={args.groupMode}
colorMode={args.colorMode}
/>
</Container>
),
@@ -433,6 +449,7 @@ export const WithCustomColors: Story = {
yAxisLabel={args.yAxisLabel}
id={args.id}
groupMode={args.groupMode}
colorMode={args.colorMode}
/>
</Container>
),
@@ -469,6 +486,7 @@ export const SingleSeries: Story = {
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -510,6 +528,7 @@ export const Currency: Story = {
yAxisLabel={args.yAxisLabel}
id={args.id}
groupMode={args.groupMode}
colorMode={args.colorMode}
/>
</Container>
),
@@ -550,6 +569,7 @@ export const NegativeValues: Story = {
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -618,6 +638,7 @@ export const MixedPositiveNegative: Story = {
yAxisLabel={args.yAxisLabel}
groupMode={args.groupMode}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -650,6 +671,7 @@ export const AllNegative: Story = {
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -686,6 +708,7 @@ export const TemperatureData: Story = {
yAxisLabel={args.yAxisLabel}
suffix={args.suffix}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -754,6 +777,7 @@ export const StackedNegative: Story = {
xAxisLabel={args.xAxisLabel}
yAxisLabel={args.yAxisLabel}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -815,6 +839,7 @@ export const GroupedWithAllBarsTooltip: Story = {
yAxisLabel={args.yAxisLabel}
groupMode={args.groupMode}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -870,6 +895,7 @@ export const GroupedDefaultTooltip: Story = {
yAxisLabel={args.yAxisLabel}
groupMode={args.groupMode}
id={args.id}
colorMode={args.colorMode}
/>
</Container>
),
@@ -925,6 +951,7 @@ export const Catalog: Story = {
id={`bar-chart-catalog-${args.keys?.length ?? 0}-${
args.groupMode ?? 'grouped'
}`}
colorMode={args.colorMode}
/>
</Container>
),
@@ -66,6 +66,17 @@ const meta: Meta<typeof GraphWidgetLineChart> = {
omitNullValues: {
control: 'boolean',
},
colorMode: {
control: 'select',
options: [
'automaticPalette',
'explicitSingleColor',
'selectFieldOptionColors',
],
},
},
args: {
colorMode: 'automaticPalette',
},
};
@@ -95,6 +106,7 @@ const renderChart = (args: ChartArgs) => (
prefix={args.prefix}
suffix={args.suffix}
decimals={args.decimals}
colorMode={args.colorMode}
/>
</Container>
);
@@ -75,6 +75,17 @@ const meta: Meta<typeof GraphWidgetPieChart> = {
showDataLabels: {
control: 'boolean',
},
colorMode: {
control: 'select',
options: [
'automaticPalette',
'explicitSingleColor',
'selectFieldOptionColors',
],
},
},
args: {
colorMode: 'automaticPalette',
},
};
@@ -109,6 +120,7 @@ export const Default: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -132,6 +144,7 @@ export const WithCenterMetric: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -155,6 +168,7 @@ export const WithDataLabels: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -184,6 +198,7 @@ export const Revenue: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -212,6 +227,7 @@ export const TaskStatus: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -239,6 +255,7 @@ export const TwoSlices: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -271,6 +288,7 @@ export const ManySlices: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -299,6 +317,7 @@ export const WithoutLegend: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -328,6 +347,7 @@ export const MarketShare: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -357,6 +377,7 @@ export const Storage: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -421,6 +442,7 @@ export const Catalog: Story = {
id={args.id}
objectMetadataItemId={mockObjectMetadataItemId}
configuration={mockConfiguration}
colorMode={args.colorMode}
/>
</Container>
),
@@ -18,6 +18,7 @@ import { getBarChartAxisConfigs } from '@/page-layout/widgets/graph/graphWidgetB
import { getBarChartColor } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartColor';
import { getBarChartInnerPadding } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartInnerPadding';
import { getBarChartTickConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { computeEffectiveValueRange } from '@/page-layout/widgets/graph/utils/computeEffectiveValueRange';
import { computeValueTickValues } from '@/page-layout/widgets/graph/utils/computeValueTickValues';
import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry';
@@ -57,6 +58,7 @@ type GraphWidgetBarChartProps = {
id: string;
layout?: BarChartLayout;
groupMode?: 'grouped' | 'stacked';
colorMode: GraphColorMode;
seriesLabels?: Record<string, string>;
rangeMin?: number;
rangeMax?: number;
@@ -86,6 +88,7 @@ export const GraphWidgetBarChart = ({
id,
layout = BarChartLayout.VERTICAL,
groupMode,
colorMode,
seriesLabels,
rangeMin,
rangeMax,
@@ -130,6 +133,7 @@ export const GraphWidgetBarChart = ({
series,
colorRegistry,
seriesLabels,
colorMode,
});
const calculatedValueRange =
@@ -47,6 +47,7 @@ export const GraphWidgetBarChartRenderer = () => {
loading,
hasTooManyGroups,
formattedToRawLookup,
colorMode,
objectMetadataItem,
} = useGraphBarChartWidgetData({
objectMetadataItemId: widget.objectMetadataId,
@@ -123,6 +124,7 @@ export const GraphWidgetBarChartRenderer = () => {
showLegend={showLegend}
layout={layout}
groupMode={groupMode}
colorMode={colorMode}
id={widget.id}
displayType="shortNumber"
rangeMin={configuration.rangeMin ?? undefined}
@@ -76,6 +76,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -91,6 +92,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -119,6 +121,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -150,6 +153,7 @@ describe('useBarChartData', () => {
series: undefined,
colorRegistry: mockColorRegistry,
seriesLabels: { sales: 'Revenue', costs: 'Expenses' },
colorMode: 'automaticPalette',
}),
);
@@ -165,6 +169,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -179,6 +184,7 @@ describe('useBarChartData', () => {
keys: [],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -195,6 +201,7 @@ describe('useBarChartData', () => {
series: undefined,
colorRegistry: mockColorRegistry,
seriesLabels: undefined,
colorMode: 'automaticPalette',
}),
);
@@ -210,6 +217,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -217,7 +225,7 @@ describe('useBarChartData', () => {
expect(result.current.legendItems[0]).toMatchObject({
id: 'sales',
label: 'Sales',
color: 'green5',
color: 'greenSolid',
});
});
@@ -231,6 +239,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -249,6 +258,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -265,6 +275,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -282,6 +293,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -301,6 +313,7 @@ describe('useBarChartData', () => {
keys: ['sales', 'costs'],
series: mockSeries,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -3,8 +3,10 @@ import { type BarChartConfig } from '@/page-layout/widgets/graph/graphWidgetBarC
import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { graphWidgetHiddenLegendIdsComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHiddenLegendIdsComponentState';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { type BarDatum } from '@nivo/bar';
import { useMemo } from 'react';
@@ -18,6 +20,7 @@ type UseBarChartDataProps = {
colorRegistry: GraphColorRegistry;
seriesLabels?: Record<string, string>;
groupMode?: 'grouped' | 'stacked';
colorMode: GraphColorMode;
};
export const useBarChartData = ({
@@ -27,6 +30,7 @@ export const useBarChartData = ({
series,
colorRegistry,
seriesLabels,
colorMode,
}: UseBarChartDataProps) => {
const hiddenLegendIds = useRecoilComponentValue(
graphWidgetHiddenLegendIdsComponentState,
@@ -37,13 +41,15 @@ export const useBarChartData = ({
[series],
);
const shouldApplyGradient = colorMode === 'explicitSingleColor';
const allEnrichedKeys: BarChartEnrichedKey[] = keys.map((key, index) => {
const seriesConfig = seriesConfigMap.get(key);
const colorScheme = getColorScheme({
registry: colorRegistry,
colorName: seriesConfig?.color,
fallbackIndex: index,
totalGroups: keys.length,
totalGroups: shouldApplyGradient ? keys.length : undefined,
});
return {
@@ -68,21 +74,31 @@ export const useBarChartData = ({
const barConfigs = useMemo((): BarChartConfig[] => {
return data.flatMap((dataPoint) => {
const indexValue = dataPoint[indexBy];
const datumColor = parseGraphColor(dataPoint.color as string | undefined);
return visibleKeys.flatMap((key): BarChartConfig[] => {
const enrichedKey = allEnrichedKeys.find((ek) => ek.key === key);
if (!isDefined(enrichedKey)) {
return [];
}
const colorScheme = isDefined(datumColor)
? getColorScheme({
registry: colorRegistry,
colorName: datumColor,
})
: enrichedKey.colorScheme;
return [
{
key,
indexValue,
colorScheme: enrichedKey.colorScheme,
colorScheme,
},
];
});
});
}, [data, indexBy, visibleKeys, allEnrichedKeys]);
}, [data, indexBy, visibleKeys, allEnrichedKeys, colorRegistry]);
return {
seriesConfigMap,
@@ -4,6 +4,7 @@ import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarC
import { getBarChartQueryLimit } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit';
import { transformGroupByDataToBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData';
import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek';
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
@@ -33,6 +34,7 @@ type UseGraphBarChartWidgetDataResult = {
error?: Error;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
objectMetadataItem: ReturnType<
typeof useObjectMetadataItemById
>['objectMetadataItem'];
@@ -1,8 +1,11 @@
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { sortBarChartDataBySecondaryDimensionSum } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { determineGraphColorMode } from '@/page-layout/widgets/graph/utils/determineGraphColorMode';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { determineChartItemColor } from '@/page-layout/widgets/graph/utils/determineChartItemColor';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { sortSecondaryAxisData } from '@/page-layout/widgets/graph/utils/sortSecondaryAxisData';
import { sortTwoDimensionalChartPrimaryAxisDataByFieldOrManuallyIfNeeded } from '@/page-layout/widgets/graph/utils/sortTwoDimensionalChartPrimaryAxisDataByFieldOrManuallyIfNeeded';
import { type BarDatum } from '@nivo/bar';
@@ -28,6 +31,7 @@ type SortTwoDimensionalBarChartDataResult = {
sortedData: BarDatum[];
sortedKeys: string[];
sortedSeries: BarChartSeries[];
colorMode: GraphColorMode;
};
export const sortTwoDimensionalBarChartData = ({
@@ -59,11 +63,19 @@ export const sortTwoDimensionalBarChartData = ({
subFieldName: secondaryAxisSubFieldName,
});
const sortedSeries: BarChartSeries[] = sortedKeys.map((key) => ({
key,
label: key,
color: color as GraphColor,
}));
const sortedSeries: BarChartSeries[] = sortedKeys.map((key) => {
const rawValue = secondaryAxisFormattedToRawLookup?.get(key);
return {
key,
label: key,
color: determineChartItemColor({
configurationColor: parseGraphColor(color),
selectOptions: secondaryAxisSelectFieldOptions,
rawValue: isDefined(rawValue) ? String(rawValue) : key,
}),
};
});
let sortedData: BarDatum[] = data;
@@ -90,9 +102,15 @@ export const sortTwoDimensionalBarChartData = ({
}
}
const colorMode = determineGraphColorMode({
configurationColor: color,
selectFieldOptions: secondaryAxisSelectFieldOptions,
});
return {
sortedData,
sortedKeys,
sortedSeries,
colorMode,
};
};
@@ -8,6 +8,7 @@ import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarC
import { fillDateGapsInBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData';
import { transformOneDimensionalGroupByToBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData';
import { transformTwoDimensionalGroupByToBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults';
@@ -47,6 +48,7 @@ type TransformGroupByDataToBarChartDataResult = {
layout?: BarChartLayout;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
const EMPTY_BAR_CHART_RESULT: Omit<
@@ -62,6 +64,7 @@ const EMPTY_BAR_CHART_RESULT: Omit<
layout: BarChartLayout.VERTICAL,
hasTooManyGroups: false,
formattedToRawLookup: new Map(),
colorMode: 'automaticPalette',
};
export const transformGroupByDataToBarChartData = ({
@@ -1,19 +1,22 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { GRAPH_DEFAULT_COLOR } from '@/page-layout/widgets/graph/constants/GraphDefaultColor.constant';
import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { applyCumulativeTransformToBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/applyCumulativeTransformToBarChartData';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { determineGraphColorMode } from '@/page-layout/widgets/graph/utils/determineGraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { determineChartItemColor } from '@/page-layout/widgets/graph/utils/determineChartItemColor';
import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { processOneDimensionalGroupByResults } from '@/page-layout/widgets/graph/utils/processOneDimensionalGroupByResults';
import { sortChartDataIfNeeded } from '@/page-layout/widgets/graph/utils/sortChartDataIfNeeded';
import { type BarDatum } from '@nivo/bar';
import {
type FirstDayOfTheWeek,
isDefined,
isFieldMetadataSelectKind,
type FirstDayOfTheWeek,
} from 'twenty-shared/utils';
import { type BarChartConfiguration } from '~/generated/graphql';
@@ -36,6 +39,7 @@ type TransformOneDimensionalGroupByToBarChartDataResult = {
series: BarChartSeries[];
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
export const transformOneDimensionalGroupByToBarChartData = ({
@@ -73,10 +77,21 @@ export const transformOneDimensionalGroupByToBarChartData = ({
});
const unsortedData: BarDatum[] = processedDataPoints.map(
({ xValue, aggregateValue }) => ({
[indexByKey]: xValue,
[aggregateValueKey]: aggregateValue,
}),
({ xValue, rawXValue, aggregateValue }) => {
const color = determineChartItemColor({
configurationColor: parseGraphColor(configuration.color),
selectOptions: isFieldMetadataSelectKind(groupByFieldX.type)
? groupByFieldX.options
: undefined,
rawValue: isDefined(rawXValue) ? String(rawXValue) : null,
});
return {
[indexByKey]: xValue,
[aggregateValueKey]: aggregateValue,
...(isDefined(color) && { color }),
};
},
);
const sortedData = sortChartDataIfNeeded({
@@ -100,7 +115,6 @@ export const transformOneDimensionalGroupByToBarChartData = ({
{
key: aggregateValueKey,
label: aggregateField.label,
color: (configuration.color ?? GRAPH_DEFAULT_COLOR) as GraphColor,
},
];
@@ -113,6 +127,13 @@ export const transformOneDimensionalGroupByToBarChartData = ({
})
: limitedSortedData;
const colorMode = determineGraphColorMode({
configurationColor: configuration.color,
selectFieldOptions: isFieldMetadataSelectKind(groupByFieldX.type)
? groupByFieldX.options
: undefined,
});
return {
data: finalData,
indexBy: indexByKey,
@@ -121,5 +142,6 @@ export const transformOneDimensionalGroupByToBarChartData = ({
hasTooManyGroups:
rawResults.length > BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS,
formattedToRawLookup,
colorMode,
};
};
@@ -5,6 +5,7 @@ import { applyCumulativeTransformToTwoDimensionalBarChartData } from '@/page-lay
import { buildTwoDimensionalBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/buildTwoDimensionalBarChartData';
import { limitTwoDimensionalBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/limitTwoDimensionalBarChartData';
import { sortTwoDimensionalBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/sortTwoDimensionalBarChartData';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey';
@@ -34,6 +35,7 @@ type TransformTwoDimensionalGroupByToBarChartDataResult = {
series: BarChartSeries[];
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
export const transformTwoDimensionalGroupByToBarChartData = ({
@@ -72,7 +74,7 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
indexByKey,
});
const { sortedData, sortedKeys, sortedSeries } =
const { sortedData, sortedKeys, sortedSeries, colorMode } =
sortTwoDimensionalBarChartData({
data: unsortedData,
keys: Array.from(yValues),
@@ -113,5 +115,6 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
series: limitedSeries,
hasTooManyGroups,
formattedToRawLookup,
colorMode,
};
};
@@ -19,6 +19,7 @@ import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLin
import { calculateValueRangeFromLineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/calculateValueRangeFromLineChartSeries';
import { getLineChartAxisBottomConfig } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisBottomConfig';
import { getLineChartAxisLeftConfig } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisLeftConfig';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { computeEffectiveValueRange } from '@/page-layout/widgets/graph/utils/computeEffectiveValueRange';
import { computeValueTickValues } from '@/page-layout/widgets/graph/utils/computeValueTickValues';
import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry';
@@ -60,6 +61,7 @@ type GraphWidgetLineChartProps = {
rangeMax?: number;
omitNullValues?: boolean;
groupMode?: 'stacked';
colorMode: GraphColorMode;
onSliceClick?: (point: Point<LineSeries>) => void;
} & GraphValueFormatOptions;
@@ -86,6 +88,7 @@ export const GraphWidgetLineChart = ({
omitNullValues: _omitNullValues = false,
displayType,
groupMode,
colorMode,
decimals,
prefix,
suffix,
@@ -111,6 +114,7 @@ export const GraphWidgetLineChart = ({
data,
colorRegistry,
id,
colorMode,
});
const calculatedValueRange =
@@ -43,6 +43,7 @@ export const GraphWidgetLineChartRenderer = () => {
hasTooManyGroups,
loading,
formattedToRawLookup,
colorMode,
objectMetadataItem,
} = useGraphLineChartWidgetData({
objectMetadataItemId: widget.objectMetadataId,
@@ -124,6 +125,7 @@ export const GraphWidgetLineChartRenderer = () => {
rangeMax={configuration.rangeMax ?? undefined}
omitNullValues={configuration.omitNullValues ?? false}
groupMode={groupMode}
colorMode={colorMode}
displayType="shortNumber"
onSliceClick={isPageLayoutInEditMode ? undefined : handlePointClick}
/>
@@ -85,6 +85,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -105,6 +106,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -134,6 +136,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -157,6 +160,7 @@ describe('useLineChartData', () => {
data: [],
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -179,6 +183,7 @@ describe('useLineChartData', () => {
data: dataWithoutLabel,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -193,6 +198,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -210,6 +216,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -224,6 +231,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -239,6 +247,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -255,6 +264,7 @@ describe('useLineChartData', () => {
data: mockData,
colorRegistry: mockColorRegistry,
id: 'test-chart',
colorMode: 'automaticPalette',
}),
);
@@ -2,9 +2,10 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMeta
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
import { getLineChartQueryLimit } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit';
import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { transformGroupByDataToLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/transformGroupByDataToLineChartData';
import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek';
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
import { useMemo } from 'react';
@@ -23,6 +24,7 @@ type UseGraphLineChartWidgetDataResult = {
showLegend: boolean;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
loading: boolean;
error?: Error;
objectMetadataItem: ReturnType<
@@ -2,6 +2,7 @@ import { type GraphWidgetLegendItem } from '@/page-layout/widgets/graph/componen
import { type LineChartEnrichedSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartEnrichedSeries';
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
import { graphWidgetHiddenLegendIdsComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHiddenLegendIdsComponentState';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -12,24 +13,28 @@ type UseLineChartDataProps = {
data: LineChartSeries[];
colorRegistry: GraphColorRegistry;
id: string;
colorMode: GraphColorMode;
};
export const useLineChartData = ({
data,
colorRegistry,
id,
colorMode,
}: UseLineChartDataProps) => {
const hiddenLegendIds = useRecoilComponentValue(
graphWidgetHiddenLegendIdsComponentState,
);
const allEnrichedSeries = useMemo((): LineChartEnrichedSeries[] => {
const shouldApplyGradient = colorMode === 'explicitSingleColor';
return data.map((series, index) => {
const colorScheme = getColorScheme({
registry: colorRegistry,
colorName: series.color,
fallbackIndex: index,
totalGroups: data.length,
totalGroups: shouldApplyGradient ? data.length : undefined,
});
const sanitizedSeriesId = series.id
@@ -40,7 +45,7 @@ export const useLineChartData = ({
return { ...series, colorScheme, areaFillId, label };
});
}, [data, colorRegistry, id]);
}, [data, colorRegistry, id, colorMode]);
const legendItems: GraphWidgetLegendItem[] = allEnrichedSeries.map(
(series) => ({
@@ -2,7 +2,11 @@ import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetad
import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint';
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
import { sortLineChartDataBySecondaryDimensionSum } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/sortLineChartDataBySecondaryDimensionSum';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { determineGraphColorMode } from '@/page-layout/widgets/graph/utils/determineGraphColorMode';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { determineChartItemColor } from '@/page-layout/widgets/graph/utils/determineChartItemColor';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { sortSecondaryAxisData } from '@/page-layout/widgets/graph/utils/sortSecondaryAxisData';
import { sortTwoDimensionalChartPrimaryAxisDataByFieldOrManuallyIfNeeded } from '@/page-layout/widgets/graph/utils/sortTwoDimensionalChartPrimaryAxisDataByFieldOrManuallyIfNeeded';
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
@@ -23,6 +27,7 @@ type SortTwoDimensionalLineChartDataConfiguration = {
type SortTwoDimensionalLineChartDataResult = {
sortedSeries: LineChartSeries[];
colorMode: GraphColorMode;
};
export const sortTwoDimensionalLineChartData = ({
@@ -32,6 +37,7 @@ export const sortTwoDimensionalLineChartData = ({
primaryAxisManualSortOrder,
secondaryAxisOrderBy,
secondaryAxisManualSortOrder,
color,
},
primaryAxisFormattedToRawLookup,
primaryAxisSelectFieldOptions,
@@ -83,7 +89,26 @@ export const sortTwoDimensionalLineChartData = ({
subFieldName: secondaryAxisSubFieldName,
});
sortedSeries = sortedSeries.map((seriesItem) => {
const rawValue = secondaryAxisFormattedToRawLookup?.get(seriesItem.id);
return {
...seriesItem,
color: determineChartItemColor({
configurationColor: parseGraphColor(color),
selectOptions: secondaryAxisSelectFieldOptions,
rawValue: isDefined(rawValue) ? String(rawValue) : seriesItem.id,
}),
};
});
const colorMode = determineGraphColorMode({
configurationColor: color,
selectFieldOptions: secondaryAxisSelectFieldOptions,
});
return {
sortedSeries,
colorMode,
};
};
@@ -4,12 +4,13 @@ import { getAggregateOperationLabel } from '@/object-record/record-board/record-
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName';
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
import { transformOneDimensionalGroupByToLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/transformOneDimensionalGroupByToLineChartData';
import { transformTwoDimensionalGroupByToLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/transformTwoDimensionalGroupByToLineChartData';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults';
import { isRelationNestedFieldDateKind } from '@/page-layout/widgets/graph/utils/isRelationNestedFieldDateKind';
import { transformOneDimensionalGroupByToLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/transformOneDimensionalGroupByToLineChartData';
import { transformTwoDimensionalGroupByToLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/transformTwoDimensionalGroupByToLineChartData';
import {
type FirstDayOfTheWeek,
isDefined,
@@ -38,6 +39,7 @@ type TransformGroupByDataToLineChartDataResult = {
showLegend: boolean;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
const EMPTY_LINE_CHART_RESULT: Omit<
@@ -49,6 +51,7 @@ const EMPTY_LINE_CHART_RESULT: Omit<
showLegend: true,
hasTooManyGroups: false,
formattedToRawLookup: new Map(),
colorMode: 'automaticPalette',
};
export const transformGroupByDataToLineChartData = ({
@@ -5,9 +5,11 @@ import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLin
import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint';
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
import { applyCumulativeTransformToLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/applyCumulativeTransformToLineChartData';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { determineGraphColorMode } from '@/page-layout/widgets/graph/utils/determineGraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { processOneDimensionalGroupByResults } from '@/page-layout/widgets/graph/utils/processOneDimensionalGroupByResults';
import { sortChartDataIfNeeded } from '@/page-layout/widgets/graph/utils/sortChartDataIfNeeded';
import {
@@ -32,6 +34,7 @@ type TransformOneDimensionalGroupByToLineChartDataResult = {
series: LineChartSeries[];
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
export const transformOneDimensionalGroupByToLineChartData = ({
@@ -94,15 +97,23 @@ export const transformOneDimensionalGroupByToLineChartData = ({
{
id: aggregateField.name,
label: aggregateField.label,
color: (configuration.color ?? GRAPH_DEFAULT_COLOR) as GraphColor,
color: parseGraphColor(configuration.color) ?? GRAPH_DEFAULT_COLOR,
data: transformedData,
},
];
const colorMode = determineGraphColorMode({
configurationColor: configuration.color,
selectFieldOptions: isFieldMetadataSelectKind(groupByFieldX.type)
? groupByFieldX.options
: undefined,
});
return {
series,
hasTooManyGroups:
rawResults.length > LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS,
formattedToRawLookup,
colorMode,
};
};
@@ -6,9 +6,10 @@ import { applyCumulativeTransformToLineChartData } from '@/page-layout/widgets/g
import { buildTwoDimensionalLineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/buildTwoDimensionalLineChartSeries';
import { limitTwoDimensionalLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/limitTwoDimensionalLineChartData';
import { sortTwoDimensionalLineChartData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/sortTwoDimensionalLineChartData';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { processTwoDimensionalGroupByResults } from '@/page-layout/widgets/graph/utils/processTwoDimensionalGroupByResults';
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
import { type FirstDayOfTheWeek } from 'twenty-shared/utils';
@@ -31,6 +32,7 @@ type TransformTwoDimensionalGroupByToLineChartDataResult = {
series: LineChartSeries[];
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
export const transformTwoDimensionalGroupByToLineChartData = ({
@@ -61,10 +63,10 @@ export const transformTwoDimensionalGroupByToLineChartData = ({
const { unsortedSeries } = buildTwoDimensionalLineChartSeries({
processedDataPoints,
color: configuration.color as GraphColor,
color: parseGraphColor(configuration.color),
});
const { sortedSeries } = sortTwoDimensionalLineChartData({
const { sortedSeries, colorMode } = sortTwoDimensionalLineChartData({
series: unsortedSeries,
configuration,
primaryAxisFormattedToRawLookup: formattedToRawLookup,
@@ -99,5 +101,6 @@ export const transformTwoDimensionalGroupByToLineChartData = ({
series: finalSeries,
hasTooManyGroups,
formattedToRawLookup,
colorMode,
};
};
@@ -9,6 +9,7 @@ import { usePieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart
import { graphWidgetPieTooltipComponentState } from '@/page-layout/widgets/graph/graphWidgetPieChart/states/graphWidgetPieTooltipComponentState';
import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem';
import { getPieChartFormattedValue } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/getPieChartFormattedValue';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry';
import { type GraphValueFormatOptions } from '@/page-layout/widgets/graph/utils/graphFormatters';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
@@ -34,6 +35,7 @@ type GraphWidgetPieChartProps = {
id: string;
objectMetadataItemId: string;
configuration: PieChartConfiguration;
colorMode: GraphColorMode;
onSliceClick?: (datum: PieChartDataItem) => void;
showDataLabels?: boolean;
showCenterMetric?: boolean;
@@ -72,6 +74,7 @@ export const GraphWidgetPieChart = ({
id,
objectMetadataItemId,
configuration,
colorMode,
displayType,
decimals,
prefix,
@@ -99,6 +102,7 @@ export const GraphWidgetPieChart = ({
const { enrichedData, legendItems } = usePieChartData({
data,
colorRegistry,
colorMode,
});
const handleSliceMove = useCallback(
@@ -40,6 +40,7 @@ export const GraphWidgetPieChartRenderer = () => {
showDataLabels,
showCenterMetric,
formattedToRawLookup,
colorMode,
} = useGraphPieChartWidgetData({
objectMetadataItemId: widget.objectMetadataId,
configuration: widget.configuration,
@@ -98,6 +99,7 @@ export const GraphWidgetPieChartRenderer = () => {
objectMetadataItemId={widget.objectMetadataId}
configuration={widget.configuration}
showLegend={showLegend}
colorMode={colorMode}
displayType="shortNumber"
onSliceClick={isPageLayoutInEditMode ? undefined : handleSliceClick}
showDataLabels={showDataLabels}
@@ -67,6 +67,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -86,6 +87,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: [],
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -99,6 +101,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: singleData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -110,6 +113,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -122,6 +126,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -140,6 +145,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -157,6 +163,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -170,6 +177,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -184,6 +192,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -199,6 +208,7 @@ describe('usePieChartData', () => {
usePieChartData({
data: mockData,
colorRegistry: mockColorRegistry,
colorMode: 'automaticPalette',
}),
);
@@ -5,6 +5,7 @@ import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/
import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem';
import { transformGroupByDataToPieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData';
import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek';
import { useUserTimezone } from '@/ui/input/components/internal/date/hooks/useUserTimezone';
@@ -24,6 +25,7 @@ type UseGraphPieChartWidgetDataResult = {
hasTooManyGroups: boolean;
objectMetadataItem: ObjectMetadataItem;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
showDataLabels: boolean;
showCenterMetric: boolean;
};
@@ -3,6 +3,7 @@ import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPi
import { type PieChartEnrichedData } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData';
import { calculatePieChartPercentage } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartPercentage';
import { graphWidgetHiddenLegendIdsComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHiddenLegendIdsComponentState';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -11,11 +12,13 @@ import { useMemo } from 'react';
type UsePieChartDataProps = {
data: PieChartDataItem[];
colorRegistry: GraphColorRegistry;
colorMode: GraphColorMode;
};
export const usePieChartData = ({
data,
colorRegistry,
colorMode,
}: UsePieChartDataProps) => {
const hiddenLegendIds = useRecoilComponentValue(
graphWidgetHiddenLegendIdsComponentState,
@@ -24,12 +27,14 @@ export const usePieChartData = ({
const allEnrichedData = useMemo((): PieChartEnrichedData[] => {
const totalValue = data.reduce((sum, item) => sum + item.value, 0);
const shouldApplyGradient = colorMode === 'explicitSingleColor';
return data.map((item, index) => {
const colorScheme = getColorScheme({
registry: colorRegistry,
colorName: item.color,
fallbackIndex: index,
totalGroups: data.length,
totalGroups: shouldApplyGradient ? data.length : undefined,
});
const percentage = calculatePieChartPercentage(item.value, totalValue);
@@ -40,7 +45,7 @@ export const usePieChartData = ({
percentage,
};
});
}, [data, colorRegistry]);
}, [data, colorRegistry, colorMode]);
const legendItems: GraphWidgetLegendItem[] = allEnrichedData.map((item) => ({
id: item.id,
@@ -1,4 +1,3 @@
import { GRAPH_DEFAULT_COLOR } from '@/page-layout/widgets/graph/constants/GraphDefaultColor.constant';
import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant';
import { transformGroupByDataToPieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData';
import { FirstDayOfTheWeek } from 'twenty-shared/types';
@@ -83,8 +82,8 @@ describe('transformGroupByDataToPieChartData', () => {
});
expect(result.data).toEqual([
{ id: 'Not Set', value: 2, color: GRAPH_DEFAULT_COLOR },
{ id: 'Active', value: 5, color: GRAPH_DEFAULT_COLOR },
{ id: 'Not Set', value: 2, color: undefined },
{ id: 'Active', value: 5, color: undefined },
]);
expect(result.formattedToRawLookup.get('Not Set')).toBeNull();
expect(result.formattedToRawLookup.get('Active')).toBe('Active');
@@ -1,12 +1,14 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName';
import { GRAPH_DEFAULT_COLOR } from '@/page-layout/widgets/graph/constants/GraphDefaultColor.constant';
import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant';
import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { determineGraphColorMode } from '@/page-layout/widgets/graph/utils/determineGraphColorMode';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { determineChartItemColor } from '@/page-layout/widgets/graph/utils/determineChartItemColor';
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
import { processOneDimensionalGroupByResults } from '@/page-layout/widgets/graph/utils/processOneDimensionalGroupByResults';
import { sortChartDataIfNeeded } from '@/page-layout/widgets/graph/utils/sortChartDataIfNeeded';
import { type FirstDayOfTheWeek } from 'twenty-shared/types';
@@ -27,6 +29,7 @@ type TransformGroupByDataToPieChartDataResult = {
showLegend: boolean;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
colorMode: GraphColorMode;
};
const EMPTY_PIE_CHART_RESULT: TransformGroupByDataToPieChartDataResult = {
@@ -34,6 +37,7 @@ const EMPTY_PIE_CHART_RESULT: TransformGroupByDataToPieChartDataResult = {
showLegend: true,
hasTooManyGroups: false,
formattedToRawLookup: new Map(),
colorMode: 'automaticPalette',
};
export const transformGroupByDataToPieChartData = ({
@@ -99,12 +103,22 @@ export const transformGroupByDataToPieChartData = ({
};
const unsortedDataWithRawValues: PieChartDataItemWithRawValue[] =
limitedProcessedDataPoints.map(({ xValue, rawXValue, aggregateValue }) => ({
id: xValue,
value: aggregateValue,
color: (configuration.color ?? GRAPH_DEFAULT_COLOR) as GraphColor,
rawValue: isDefined(rawXValue) ? String(rawXValue) : null,
}));
limitedProcessedDataPoints.map(({ xValue, rawXValue, aggregateValue }) => {
const rawValueString = isDefined(rawXValue) ? String(rawXValue) : null;
return {
id: xValue,
value: aggregateValue,
color: determineChartItemColor({
configurationColor: parseGraphColor(configuration.color),
selectOptions: isFieldMetadataSelectKind(groupByField.type)
? groupByField.options
: undefined,
rawValue: rawValueString,
}),
rawValue: rawValueString,
};
});
const sortedDataWithRawValues = sortChartDataIfNeeded({
data: unsortedDataWithRawValues,
@@ -124,11 +138,19 @@ export const transformGroupByDataToPieChartData = ({
const showLegend = configuration.displayLegend ?? true;
const colorMode = determineGraphColorMode({
configurationColor: configuration.color,
selectFieldOptions: isFieldMetadataSelectKind(groupByField.type)
? groupByField.options
: undefined,
});
return {
data,
showLegend,
hasTooManyGroups:
filteredResults.length > PIE_CHART_MAXIMUM_NUMBER_OF_SLICES,
formattedToRawLookup,
colorMode,
};
};
@@ -1,12 +1,3 @@
export type GraphColor =
| 'auto'
| 'blue'
| 'purple'
| 'turquoise'
| 'orange'
| 'pink'
| 'yellow'
| 'red'
| 'green'
| 'sky'
| 'gray';
import { type ThemeColor } from 'twenty-ui/theme';
export type GraphColor = ThemeColor | 'auto';
@@ -0,0 +1,4 @@
export type GraphColorMode =
| 'explicitSingleColor'
| 'selectFieldOptionColors'
| 'automaticPalette';
@@ -0,0 +1,84 @@
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { determineChartItemColor } from '@/page-layout/widgets/graph/utils/determineChartItemColor';
describe('determineChartItemColor', () => {
const mockOptions: FieldMetadataItemOption[] = [
{ id: '1', value: 'ACTIVE', label: 'Active', position: 0, color: 'green' },
{
id: '2',
value: 'INACTIVE',
label: 'Inactive',
position: 1,
color: 'red',
},
];
describe('priority 1: explicit configuration color', () => {
it('should return configurationColor when set and not "auto"', () => {
expect(
determineChartItemColor({
configurationColor: 'blue',
selectOptions: mockOptions,
rawValue: 'ACTIVE', // Would be 'green' if select option was used
}),
).toBe('blue');
});
});
describe('priority 2: select option color', () => {
it('should use select option color when configurationColor is "auto"', () => {
expect(
determineChartItemColor({
configurationColor: 'auto',
selectOptions: mockOptions,
rawValue: 'ACTIVE',
}),
).toBe('green');
});
it.each([null, undefined])(
'should use select option color when configurationColor is %s (null and undefined handled identically)',
(configurationColor) => {
expect(
determineChartItemColor({
configurationColor,
selectOptions: mockOptions,
rawValue: 'INACTIVE',
}),
).toBe('red');
},
);
});
describe('priority 3: return undefined (no fallback)', () => {
it('should return undefined when no match and no selectOptions', () => {
expect(
determineChartItemColor({
configurationColor: 'auto',
selectOptions: undefined,
rawValue: 'ACTIVE',
}),
).toBeUndefined();
});
it('should return undefined when rawValue does not match any option', () => {
expect(
determineChartItemColor({
configurationColor: 'auto',
selectOptions: mockOptions,
rawValue: 'UNKNOWN',
}),
).toBeUndefined();
});
it('should return undefined when rawValue is null', () => {
expect(
determineChartItemColor({
configurationColor: 'auto',
selectOptions: mockOptions,
rawValue: null,
}),
).toBeUndefined();
});
});
});
@@ -0,0 +1,85 @@
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { getSelectOptionColorForValue } from '@/page-layout/widgets/graph/utils/getSelectOptionColorForValue';
describe('getSelectOptionColorForValue', () => {
const mockOptions: FieldMetadataItemOption[] = [
{ id: '1', value: 'ACTIVE', label: 'Active', position: 0, color: 'green' },
{
id: '2',
value: 'INACTIVE',
label: 'Inactive',
position: 1,
color: 'red',
},
];
it('should return the color for a matching option value', () => {
expect(
getSelectOptionColorForValue({
rawValue: 'ACTIVE',
selectOptions: mockOptions,
}),
).toBe('green');
});
it('should return undefined when rawValue is null or undefined', () => {
expect(
getSelectOptionColorForValue({
rawValue: null,
selectOptions: mockOptions,
}),
).toBeUndefined();
expect(
getSelectOptionColorForValue({
rawValue: undefined,
selectOptions: mockOptions,
}),
).toBeUndefined();
});
it('should return undefined when selectOptions is null, undefined, or empty', () => {
expect(
getSelectOptionColorForValue({
rawValue: 'ACTIVE',
selectOptions: null,
}),
).toBeUndefined();
expect(
getSelectOptionColorForValue({
rawValue: 'ACTIVE',
selectOptions: undefined,
}),
).toBeUndefined();
expect(
getSelectOptionColorForValue({
rawValue: 'ACTIVE',
selectOptions: [],
}),
).toBeUndefined();
});
it('should return undefined when value does not match any option', () => {
expect(
getSelectOptionColorForValue({
rawValue: 'UNKNOWN',
selectOptions: mockOptions,
}),
).toBeUndefined();
});
it('should return undefined when matching option has no color', () => {
const optionsWithoutColor = [
{ id: '1', value: 'NO_COLOR', label: 'No Color', position: 0 },
] as FieldMetadataItemOption[];
expect(
getSelectOptionColorForValue({
rawValue: 'NO_COLOR',
selectOptions: optionsWithoutColor,
}),
).toBeUndefined();
});
});
@@ -0,0 +1,31 @@
import { parseGraphColor } from '@/page-layout/widgets/graph/utils/parseGraphColor';
describe('parseGraphColor', () => {
it('should return "auto" for "auto" string', () => {
expect(parseGraphColor('auto')).toBe('auto');
});
it('should return valid ThemeColor values', () => {
expect(parseGraphColor('green')).toBe('green');
expect(parseGraphColor('blue')).toBe('blue');
expect(parseGraphColor('red')).toBe('red');
expect(parseGraphColor('purple')).toBe('purple');
});
it('should return undefined for null', () => {
expect(parseGraphColor(null)).toBeUndefined();
});
it('should return undefined for undefined', () => {
expect(parseGraphColor(undefined)).toBeUndefined();
});
it('should return undefined for invalid color strings', () => {
expect(parseGraphColor('invalid-color')).toBeUndefined();
expect(parseGraphColor('notacolor')).toBeUndefined();
});
it('should return undefined for empty string', () => {
expect(parseGraphColor('')).toBeUndefined();
});
});
@@ -0,0 +1,32 @@
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { getSelectOptionColorForValue } from '@/page-layout/widgets/graph/utils/getSelectOptionColorForValue';
import { isDefined } from 'twenty-shared/utils';
type DetermineChartItemColorParams = {
configurationColor: GraphColor | null | undefined;
selectOptions: FieldMetadataItemOption[] | null | undefined;
rawValue: string | null | undefined;
};
export const determineChartItemColor = ({
configurationColor,
selectOptions,
rawValue,
}: DetermineChartItemColorParams): GraphColor | undefined => {
if (isDefined(configurationColor) && configurationColor !== 'auto') {
return configurationColor;
}
if (isDefined(selectOptions)) {
const optionColor = getSelectOptionColorForValue({
rawValue,
selectOptions,
});
if (isDefined(optionColor)) {
return optionColor;
}
}
return undefined;
};
@@ -0,0 +1,24 @@
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
import { isDefined } from 'twenty-shared/utils';
type DetermineGraphColorModeParams = {
configurationColor: string | null | undefined;
selectFieldOptions: FieldMetadataItemOption[] | null | undefined;
};
export const determineGraphColorMode = ({
configurationColor,
selectFieldOptions,
}: DetermineGraphColorModeParams): GraphColorMode => {
const hasExplicitColor =
isDefined(configurationColor) && configurationColor !== 'auto';
const hasSelectFieldOptions =
isDefined(selectFieldOptions) && selectFieldOptions.length > 0;
return hasExplicitColor
? 'explicitSingleColor'
: hasSelectFieldOptions
? 'selectFieldOptionColors'
: 'automaticPalette';
};
@@ -0,0 +1,19 @@
import { type FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
import { isDefined } from 'twenty-shared/utils';
import { type ThemeColor } from 'twenty-ui/theme';
export const getSelectOptionColorForValue = ({
rawValue,
selectOptions,
}: {
rawValue: string | null | undefined;
selectOptions: FieldMetadataItemOption[] | null | undefined;
}): ThemeColor | undefined => {
if (!isDefined(selectOptions) || !isDefined(rawValue)) {
return undefined;
}
const option = selectOptions.find((opt) => opt.value === rawValue);
return option?.color;
};
@@ -0,0 +1,21 @@
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
import { isDefined } from 'twenty-shared/utils';
import { MAIN_COLOR_NAMES, type ThemeColor } from 'twenty-ui/theme';
export const parseGraphColor = (
value: string | null | undefined,
): GraphColor | undefined => {
if (!isDefined(value)) {
return undefined;
}
if (value === 'auto') {
return 'auto';
}
if (MAIN_COLOR_NAMES.includes(value as ThemeColor)) {
return value as GraphColor;
}
return undefined;
};