[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:
+27
@@ -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>
|
||||
),
|
||||
|
||||
+12
@@ -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>
|
||||
);
|
||||
|
||||
+22
@@ -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>
|
||||
),
|
||||
|
||||
+4
@@ -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 =
|
||||
|
||||
+2
@@ -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}
|
||||
|
||||
+14
-1
@@ -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',
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
+19
-3
@@ -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,
|
||||
|
||||
+2
@@ -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'];
|
||||
|
||||
+24
-6
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+3
@@ -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 = ({
|
||||
|
||||
+30
-8
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+4
-1
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+4
@@ -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 =
|
||||
|
||||
+2
@@ -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}
|
||||
/>
|
||||
|
||||
+10
@@ -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',
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
+4
-2
@@ -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<
|
||||
|
||||
+7
-2
@@ -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) => ({
|
||||
|
||||
+25
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+5
-2
@@ -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 = ({
|
||||
|
||||
+13
-2
@@ -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
-3
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
+4
@@ -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(
|
||||
|
||||
+2
@@ -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}
|
||||
|
||||
+10
@@ -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',
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
+2
@@ -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;
|
||||
};
|
||||
|
||||
+7
-2
@@ -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,
|
||||
|
||||
+2
-3
@@ -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');
|
||||
|
||||
+30
-8
@@ -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';
|
||||
+84
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
+85
@@ -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();
|
||||
});
|
||||
});
|
||||
+31
@@ -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();
|
||||
});
|
||||
});
|
||||
+32
@@ -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;
|
||||
};
|
||||
+24
@@ -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';
|
||||
};
|
||||
+19
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user