[Dashboards] Improve bar chart performance (#17399)
This commit is contained in:
+18
-4
@@ -1,17 +1,20 @@
|
||||
import { LEGEND_HIGHLIGHT_DIMMED_OPACITY } from '@/page-layout/widgets/graph/constants/LegendHighlightDimmedOpacity.constant';
|
||||
import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants';
|
||||
import { graphWidgetIsSliceHoveredComponentFamilySelector } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetIsSliceHoveredComponentFamilySelector';
|
||||
import { graphWidgetHighlightedLegendIdComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHighlightedLegendIdComponentState';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { type BarDatum, type BarItemProps } from '@nivo/bar';
|
||||
import { animated, to } from '@react-spring/web';
|
||||
import { isNumber } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { BarChartLayout } from '~/generated/graphql';
|
||||
|
||||
type CustomBarItemProps<D extends BarDatum> = BarItemProps<D> & {
|
||||
shouldRoundFreeEnd: boolean;
|
||||
seriesIndex: number;
|
||||
isDimmed: boolean;
|
||||
isSliceHovered: boolean;
|
||||
layout?: BarChartLayout;
|
||||
chartId?: string;
|
||||
};
|
||||
@@ -49,11 +52,22 @@ export const CustomBarItem = <D extends BarDatum>({
|
||||
ariaHidden,
|
||||
shouldRoundFreeEnd,
|
||||
seriesIndex,
|
||||
isDimmed,
|
||||
isSliceHovered,
|
||||
layout = BarChartLayout.VERTICAL,
|
||||
chartId,
|
||||
}: CustomBarItemProps<D>) => {
|
||||
const highlightedLegendId = useRecoilComponentValue(
|
||||
graphWidgetHighlightedLegendIdComponentState,
|
||||
);
|
||||
|
||||
const isSliceHovered = useRecoilComponentFamilyValue(
|
||||
graphWidgetIsSliceHoveredComponentFamilySelector,
|
||||
String(barData.indexValue),
|
||||
);
|
||||
|
||||
const isDimmed =
|
||||
isDefined(highlightedLegendId) &&
|
||||
String(highlightedLegendId) !== String(barData.id);
|
||||
|
||||
const isNegativeValue = isNumber(barData.value) && barData.value < 0;
|
||||
|
||||
const isHorizontal = layout === BarChartLayout.HORIZONTAL;
|
||||
|
||||
+4
-39
@@ -19,7 +19,6 @@ import { computeShouldRoundFreeEndMap } from '@/page-layout/widgets/graph/graphW
|
||||
import { getBarChartColor } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartColor';
|
||||
import { getBarChartInnerPadding } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartInnerPadding';
|
||||
import { getBarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartLayout';
|
||||
import { graphWidgetHighlightedLegendIdComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHighlightedLegendIdComponentState';
|
||||
import { type GraphColorMode } from '@/page-layout/widgets/graph/types/GraphColorMode';
|
||||
import { computeEffectiveValueRange } from '@/page-layout/widgets/graph/utils/computeEffectiveValueRange';
|
||||
import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry';
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
type GraphValueFormatOptions,
|
||||
} from '@/page-layout/widgets/graph/utils/graphFormatters';
|
||||
import { NodeDimensionEffect } from '@/ui/utilities/dimensions/components/NodeDimensionEffect';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -117,14 +115,6 @@ export const GraphWidgetBarChart = ({
|
||||
graphWidgetHoveredSliceIndexComponentState,
|
||||
);
|
||||
|
||||
const highlightedLegendId = useRecoilComponentValue(
|
||||
graphWidgetHighlightedLegendIdComponentState,
|
||||
);
|
||||
|
||||
const hoveredSliceIndex = useRecoilComponentValue(
|
||||
graphWidgetHoveredSliceIndexComponentState,
|
||||
);
|
||||
|
||||
const formatOptions: GraphValueFormatOptions = {
|
||||
displayType,
|
||||
decimals,
|
||||
@@ -135,16 +125,8 @@ export const GraphWidgetBarChart = ({
|
||||
|
||||
const chartTheme = useBarChartTheme();
|
||||
|
||||
const { barConfigs, enrichedKeys, legendItems, visibleKeys } =
|
||||
useBarChartData({
|
||||
data,
|
||||
indexBy,
|
||||
keys,
|
||||
series,
|
||||
colorRegistry,
|
||||
seriesLabels,
|
||||
colorMode,
|
||||
});
|
||||
const { enrichedKeysMap, enrichedKeys, legendItems, visibleKeys } =
|
||||
useBarChartData({ keys, series, colorRegistry, seriesLabels, colorMode });
|
||||
|
||||
const orderedKeys =
|
||||
groupMode === 'stacked' && layout === BarChartLayout.VERTICAL
|
||||
@@ -255,35 +237,18 @@ export const GraphWidgetBarChart = ({
|
||||
const shouldRoundFreeEnd = shouldRoundFreeEndMap?.get(barKey) ?? true;
|
||||
const seriesIndex = keyToIndexMap.get(String(props.bar.data.id)) ?? -1;
|
||||
|
||||
const isDimmed =
|
||||
isDefined(highlightedLegendId) &&
|
||||
String(highlightedLegendId) !== String(props.bar.data.id);
|
||||
|
||||
const isSliceHovered =
|
||||
isDefined(hoveredSliceIndex) &&
|
||||
String(hoveredSliceIndex) === String(props.bar.data.indexValue);
|
||||
|
||||
return (
|
||||
<CustomBarItem
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
shouldRoundFreeEnd={shouldRoundFreeEnd}
|
||||
seriesIndex={seriesIndex}
|
||||
isDimmed={isDimmed}
|
||||
isSliceHovered={isSliceHovered}
|
||||
layout={layout}
|
||||
chartId={id}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[
|
||||
shouldRoundFreeEndMap,
|
||||
keyToIndexMap,
|
||||
highlightedLegendId,
|
||||
hoveredSliceIndex,
|
||||
layout,
|
||||
id,
|
||||
],
|
||||
[shouldRoundFreeEndMap, keyToIndexMap, layout, id],
|
||||
);
|
||||
|
||||
const TotalsLayer = ({
|
||||
@@ -377,7 +342,7 @@ export const GraphWidgetBarChart = ({
|
||||
clamp: true,
|
||||
}}
|
||||
indexScale={{ type: 'band', round: true }}
|
||||
colors={(datum) => getBarChartColor(datum, barConfigs, theme)}
|
||||
colors={(datum) => getBarChartColor(datum, enrichedKeysMap, theme)}
|
||||
animate
|
||||
motionConfig={CHART_MOTION_CONFIG}
|
||||
layers={[
|
||||
|
||||
+7
-71
@@ -1,7 +1,6 @@
|
||||
import { useBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData';
|
||||
import { type BarChartSeriesWithColor } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
|
||||
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
|
||||
import { type BarDatum } from '@nivo/bar';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
const mockUseRecoilComponentValue = jest.fn();
|
||||
@@ -57,12 +56,6 @@ describe('useBarChartData', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mockData: BarDatum[] = [
|
||||
{ month: 'Jan', sales: 100, costs: 80 },
|
||||
{ month: 'Feb', sales: 120, costs: 90 },
|
||||
{ month: 'Mar', sales: 150, costs: 100 },
|
||||
];
|
||||
|
||||
const mockSeries: BarChartSeriesWithColor[] = [
|
||||
{ key: 'sales', label: 'Sales', color: 'green' },
|
||||
{ key: 'costs', label: 'Costs', color: 'purple' },
|
||||
@@ -71,8 +64,6 @@ describe('useBarChartData', () => {
|
||||
it('should create series config map', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -84,11 +75,9 @@ describe('useBarChartData', () => {
|
||||
expect(result.current.seriesConfigMap.get('costs')).toEqual(mockSeries[1]);
|
||||
});
|
||||
|
||||
it('should generate bar configs for each data point and key', () => {
|
||||
it('should generate enriched keys map for O(1) color lookup', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -96,17 +85,17 @@ describe('useBarChartData', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.barConfigs).toHaveLength(6);
|
||||
expect(result.current.barConfigs[0]).toMatchObject({
|
||||
expect(result.current.enrichedKeysMap.size).toBe(2);
|
||||
expect(result.current.enrichedKeysMap.get('sales')).toMatchObject({
|
||||
key: 'sales',
|
||||
indexValue: 'Jan',
|
||||
label: 'Sales',
|
||||
colorScheme: {
|
||||
name: 'green',
|
||||
},
|
||||
});
|
||||
expect(result.current.barConfigs[1]).toMatchObject({
|
||||
expect(result.current.enrichedKeysMap.get('costs')).toMatchObject({
|
||||
key: 'costs',
|
||||
indexValue: 'Jan',
|
||||
label: 'Costs',
|
||||
colorScheme: {
|
||||
name: 'purple',
|
||||
},
|
||||
@@ -116,8 +105,6 @@ describe('useBarChartData', () => {
|
||||
it('should create enriched keys with labels', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -147,8 +134,6 @@ describe('useBarChartData', () => {
|
||||
it('should use series labels when series config is not provided', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: undefined,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -161,26 +146,9 @@ describe('useBarChartData', () => {
|
||||
expect(result.current.enrichedKeys[1].label).toBe('Expenses');
|
||||
});
|
||||
|
||||
it('should handle empty data', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: [],
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
colorMode: 'automaticPalette',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.barConfigs).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty keys', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: [],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -188,15 +156,13 @@ describe('useBarChartData', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.barConfigs).toEqual([]);
|
||||
expect(result.current.enrichedKeysMap.size).toBe(0);
|
||||
expect(result.current.enrichedKeys).toEqual([]);
|
||||
});
|
||||
|
||||
it('should fall back to key name when no label is provided', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: undefined,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -212,8 +178,6 @@ describe('useBarChartData', () => {
|
||||
it('should return legend items from all keys', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -234,8 +198,6 @@ describe('useBarChartData', () => {
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -253,8 +215,6 @@ describe('useBarChartData', () => {
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -270,8 +230,6 @@ describe('useBarChartData', () => {
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
@@ -283,33 +241,11 @@ describe('useBarChartData', () => {
|
||||
expect(result.current.legendItems).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should filter barConfigs to only include visible keys', () => {
|
||||
mockUseRecoilComponentValue.mockReturnValue(['costs']);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
colorMode: 'automaticPalette',
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.barConfigs).toHaveLength(3);
|
||||
result.current.barConfigs.forEach((config) => {
|
||||
expect(config.key).toBe('sales');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle hidden ids that do not exist in keys', () => {
|
||||
mockUseRecoilComponentValue.mockReturnValue(['nonexistent', 'alsoNotReal']);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartData({
|
||||
data: mockData,
|
||||
indexBy: 'month',
|
||||
keys: ['sales', 'costs'],
|
||||
series: mockSeries,
|
||||
colorRegistry: mockColorRegistry,
|
||||
|
||||
+22
-52
@@ -1,20 +1,14 @@
|
||||
import { type GraphWidgetLegendItem } from '@/page-layout/widgets/graph/components/GraphWidgetLegend';
|
||||
import { type BarChartConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartConfig';
|
||||
import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey';
|
||||
import { type BarChartSeriesWithColor } 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';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UseBarChartDataProps = {
|
||||
data: BarDatum[];
|
||||
indexBy: string;
|
||||
keys: string[];
|
||||
series?: BarChartSeriesWithColor[];
|
||||
colorRegistry: GraphColorRegistry;
|
||||
@@ -24,8 +18,6 @@ type UseBarChartDataProps = {
|
||||
};
|
||||
|
||||
export const useBarChartData = ({
|
||||
data,
|
||||
indexBy,
|
||||
keys,
|
||||
series,
|
||||
colorRegistry,
|
||||
@@ -44,23 +36,25 @@ export const useBarChartData = ({
|
||||
[series],
|
||||
);
|
||||
|
||||
const shouldApplyGradient = colorMode === 'explicitSingleColor';
|
||||
const allEnrichedKeys = useMemo((): BarChartEnrichedKey[] => {
|
||||
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: shouldApplyGradient ? keys.length : undefined,
|
||||
return keys.map((key, index) => {
|
||||
const seriesConfig = seriesConfigMap.get(key);
|
||||
const colorScheme = getColorScheme({
|
||||
registry: colorRegistry,
|
||||
colorName: seriesConfig?.color,
|
||||
fallbackIndex: index,
|
||||
totalGroups: shouldApplyGradient ? keys.length : undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
key,
|
||||
colorScheme,
|
||||
label: seriesConfig?.label ?? seriesLabels?.[key] ?? key,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
key,
|
||||
colorScheme,
|
||||
label: seriesConfig?.label ?? seriesLabels?.[key] ?? key,
|
||||
};
|
||||
});
|
||||
}, [keys, seriesConfigMap, colorRegistry, seriesLabels, colorMode]);
|
||||
|
||||
const legendItems: GraphWidgetLegendItem[] = allEnrichedKeys.map((item) => ({
|
||||
id: item.key,
|
||||
@@ -74,38 +68,14 @@ export const useBarChartData = ({
|
||||
(item) => !hiddenLegendIds.includes(item.key),
|
||||
);
|
||||
|
||||
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,
|
||||
},
|
||||
];
|
||||
});
|
||||
});
|
||||
}, [data, indexBy, visibleKeys, allEnrichedKeys, colorRegistry]);
|
||||
const enrichedKeysMap = useMemo(
|
||||
() => new Map(allEnrichedKeys.map((ek) => [ek.key, ek])),
|
||||
[allEnrichedKeys],
|
||||
);
|
||||
|
||||
return {
|
||||
seriesConfigMap,
|
||||
barConfigs,
|
||||
enrichedKeysMap,
|
||||
enrichedKeys,
|
||||
legendItems,
|
||||
visibleKeys,
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphColorScheme';
|
||||
|
||||
export type BarChartConfig = {
|
||||
key: string;
|
||||
indexValue: string | number;
|
||||
colorScheme: GraphColorScheme;
|
||||
};
|
||||
+39
-41
@@ -1,4 +1,4 @@
|
||||
import { type BarChartConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartConfig';
|
||||
import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey';
|
||||
import { getBarChartColor } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartColor';
|
||||
import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphColorScheme';
|
||||
import { type BarDatum, type ComputedDatum } from '@nivo/bar';
|
||||
@@ -51,36 +51,37 @@ describe('getBarChartColor', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const mockBarConfigs: BarChartConfig[] = [
|
||||
{
|
||||
key: 'sales',
|
||||
indexValue: 'January',
|
||||
colorScheme: mockBlueColorScheme,
|
||||
},
|
||||
{
|
||||
key: 'revenue',
|
||||
indexValue: 'January',
|
||||
colorScheme: mockGreenColorScheme,
|
||||
},
|
||||
{
|
||||
key: 'sales',
|
||||
indexValue: 'February',
|
||||
colorScheme: mockBlueColorScheme,
|
||||
},
|
||||
];
|
||||
const mockEnrichedKeysMap = new Map<string, BarChartEnrichedKey>([
|
||||
[
|
||||
'sales',
|
||||
{
|
||||
key: 'sales',
|
||||
label: 'Sales',
|
||||
colorScheme: mockBlueColorScheme,
|
||||
},
|
||||
],
|
||||
[
|
||||
'revenue',
|
||||
{
|
||||
key: 'revenue',
|
||||
label: 'Revenue',
|
||||
colorScheme: mockGreenColorScheme,
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
it('should return the correct color when datum matches bar config', () => {
|
||||
it('should return the correct color when datum matches enriched key', () => {
|
||||
const datum: ComputedDatum<BarDatum> = {
|
||||
id: 'sales',
|
||||
indexValue: 'January',
|
||||
} as unknown as ComputedDatum<BarDatum>;
|
||||
|
||||
const result = getBarChartColor(datum, mockBarConfigs, mockTheme);
|
||||
const result = getBarChartColor(datum, mockEnrichedKeysMap, mockTheme);
|
||||
|
||||
expect(result).toBe('#solidBlue');
|
||||
});
|
||||
|
||||
it('should return different colors for different keys at same index', () => {
|
||||
it('should return different colors for different keys', () => {
|
||||
const salesDatum: ComputedDatum<BarDatum> = {
|
||||
id: 'sales',
|
||||
indexValue: 'January',
|
||||
@@ -91,10 +92,14 @@ describe('getBarChartColor', () => {
|
||||
indexValue: 'January',
|
||||
} as unknown as ComputedDatum<BarDatum>;
|
||||
|
||||
const salesColor = getBarChartColor(salesDatum, mockBarConfigs, mockTheme);
|
||||
const salesColor = getBarChartColor(
|
||||
salesDatum,
|
||||
mockEnrichedKeysMap,
|
||||
mockTheme,
|
||||
);
|
||||
const revenueColor = getBarChartColor(
|
||||
revenueDatum,
|
||||
mockBarConfigs,
|
||||
mockEnrichedKeysMap,
|
||||
mockTheme,
|
||||
);
|
||||
|
||||
@@ -102,40 +107,33 @@ describe('getBarChartColor', () => {
|
||||
expect(revenueColor).toBe('#solidGreen');
|
||||
});
|
||||
|
||||
it('should return theme fallback color when no matching config is found', () => {
|
||||
it('should return theme fallback color when no matching key is found', () => {
|
||||
const datum: ComputedDatum<BarDatum> = {
|
||||
id: 'unknown',
|
||||
indexValue: 'January',
|
||||
} as unknown as ComputedDatum<BarDatum>;
|
||||
|
||||
const result = getBarChartColor(datum, mockBarConfigs, mockTheme);
|
||||
const result = getBarChartColor(datum, mockEnrichedKeysMap, mockTheme);
|
||||
|
||||
expect(result).toBe('#fallback');
|
||||
});
|
||||
|
||||
it('should return fallback color when indexValue does not match', () => {
|
||||
const datum: ComputedDatum<BarDatum> = {
|
||||
id: 'sales',
|
||||
indexValue: 'March',
|
||||
} as unknown as ComputedDatum<BarDatum>;
|
||||
|
||||
const result = getBarChartColor(datum, mockBarConfigs, mockTheme);
|
||||
|
||||
expect(result).toBe('#fallback');
|
||||
});
|
||||
|
||||
it('should return fallback color when barConfigs is empty', () => {
|
||||
it('should return fallback color when enrichedKeysMap is empty', () => {
|
||||
const datum: ComputedDatum<BarDatum> = {
|
||||
id: 'sales',
|
||||
indexValue: 'January',
|
||||
} as unknown as ComputedDatum<BarDatum>;
|
||||
|
||||
const result = getBarChartColor(datum, [], mockTheme);
|
||||
const result = getBarChartColor(
|
||||
datum,
|
||||
new Map<string, BarChartEnrichedKey>(),
|
||||
mockTheme,
|
||||
);
|
||||
|
||||
expect(result).toBe('#fallback');
|
||||
});
|
||||
|
||||
it('should match based on both key and indexValue', () => {
|
||||
it('should return same color for same key regardless of indexValue', () => {
|
||||
const januaryDatum: ComputedDatum<BarDatum> = {
|
||||
id: 'sales',
|
||||
indexValue: 'January',
|
||||
@@ -148,12 +146,12 @@ describe('getBarChartColor', () => {
|
||||
|
||||
const januaryColor = getBarChartColor(
|
||||
januaryDatum,
|
||||
mockBarConfigs,
|
||||
mockEnrichedKeysMap,
|
||||
mockTheme,
|
||||
);
|
||||
const februaryColor = getBarChartColor(
|
||||
februaryDatum,
|
||||
mockBarConfigs,
|
||||
mockEnrichedKeysMap,
|
||||
mockTheme,
|
||||
);
|
||||
|
||||
|
||||
+5
-8
@@ -1,18 +1,15 @@
|
||||
import { type BarChartConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartConfig';
|
||||
import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey';
|
||||
import { type BarDatum, type ComputedDatum } from '@nivo/bar';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ThemeType } from 'twenty-ui/theme';
|
||||
|
||||
export const getBarChartColor = (
|
||||
datum: ComputedDatum<BarDatum>,
|
||||
barConfigs: BarChartConfig[],
|
||||
enrichedKeysMap: Map<string, BarChartEnrichedKey>,
|
||||
theme: ThemeType,
|
||||
) => {
|
||||
const bar = barConfigs.find(
|
||||
(b) => b.key === datum.id && b.indexValue === datum.indexValue,
|
||||
);
|
||||
if (!isDefined(bar)) {
|
||||
const enrichedKey = enrichedKeysMap.get(String(datum.id));
|
||||
if (!enrichedKey) {
|
||||
return theme.border.color.light;
|
||||
}
|
||||
return bar.colorScheme.solid;
|
||||
return enrichedKey.colorScheme.solid;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user