[Dashboards] new line area colors (#16272)
before - https://github.com/user-attachments/assets/a507809a-2a01-4aa6-940d-7d273e5fc8a7 after - https://github.com/user-attachments/assets/7c4d2a50-2ba6-4b91-b526-a87c609129c8
This commit is contained in:
-12
@@ -12,10 +12,6 @@ describe('useBarChartData', () => {
|
||||
const mockColorRegistry: GraphColorRegistry = {
|
||||
green: {
|
||||
name: 'green',
|
||||
gradient: {
|
||||
normal: ['green1', 'green2'],
|
||||
hover: ['green3', 'green4'],
|
||||
},
|
||||
solid: 'greenSolid',
|
||||
variations: [
|
||||
'green1',
|
||||
@@ -34,10 +30,6 @@ describe('useBarChartData', () => {
|
||||
},
|
||||
purple: {
|
||||
name: 'purple',
|
||||
gradient: {
|
||||
normal: ['purple1', 'purple2'],
|
||||
hover: ['purple3', 'purple4'],
|
||||
},
|
||||
solid: 'purpleSolid',
|
||||
variations: [
|
||||
'purple1',
|
||||
@@ -99,7 +91,6 @@ describe('useBarChartData', () => {
|
||||
indexValue: 'Jan',
|
||||
colorScheme: {
|
||||
name: 'green',
|
||||
gradient: mockColorRegistry.green.gradient,
|
||||
},
|
||||
});
|
||||
expect(result.current.barConfigs[1]).toMatchObject({
|
||||
@@ -107,7 +98,6 @@ describe('useBarChartData', () => {
|
||||
indexValue: 'Jan',
|
||||
colorScheme: {
|
||||
name: 'purple',
|
||||
gradient: mockColorRegistry.purple.gradient,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -129,7 +119,6 @@ describe('useBarChartData', () => {
|
||||
label: 'Sales',
|
||||
colorScheme: {
|
||||
name: 'green',
|
||||
gradient: mockColorRegistry.green.gradient,
|
||||
},
|
||||
});
|
||||
expect(result.current.enrichedKeys[0].colorScheme.solid).toBeDefined();
|
||||
@@ -138,7 +127,6 @@ describe('useBarChartData', () => {
|
||||
label: 'Costs',
|
||||
colorScheme: {
|
||||
name: 'purple',
|
||||
gradient: mockColorRegistry.purple.gradient,
|
||||
},
|
||||
});
|
||||
expect(result.current.enrichedKeys[1].colorScheme.solid).toBeDefined();
|
||||
|
||||
+11
-31
@@ -2,7 +2,6 @@ import { GraphWidgetLegend } from '@/page-layout/widgets/graph/components/GraphW
|
||||
import { GraphWidgetTooltip } from '@/page-layout/widgets/graph/components/GraphWidgetTooltip';
|
||||
import { GaugeChartEndLine } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/components/GaugeChartEndLine';
|
||||
import { useGaugeChartData } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartData';
|
||||
import { useGaugeChartHandlers } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartHandlers';
|
||||
import { useGaugeChartTooltip } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/hooks/useGaugeChartTooltip';
|
||||
import { type GaugeChartData } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData';
|
||||
import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/createGraphColorRegistry';
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
type RadialBarCustomLayerProps,
|
||||
ResponsiveRadialBar,
|
||||
} from '@nivo/radial-bar';
|
||||
import { useId } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
||||
|
||||
type GraphWidgetGaugeChartProps = {
|
||||
@@ -62,7 +61,6 @@ export const GraphWidgetGaugeChart = ({
|
||||
data,
|
||||
showValue = true,
|
||||
showLegend = true,
|
||||
id,
|
||||
displayType,
|
||||
decimals,
|
||||
prefix,
|
||||
@@ -71,7 +69,6 @@ export const GraphWidgetGaugeChart = ({
|
||||
onGaugeClick,
|
||||
}: GraphWidgetGaugeChartProps) => {
|
||||
const theme = useTheme();
|
||||
const instanceId = useId();
|
||||
const colorRegistry = createGraphColorRegistry(theme);
|
||||
|
||||
const formatOptions: GraphValueFormatOptions = {
|
||||
@@ -81,24 +78,17 @@ export const GraphWidgetGaugeChart = ({
|
||||
suffix,
|
||||
customFormatter,
|
||||
};
|
||||
const handleClick = () => {
|
||||
onGaugeClick?.(data);
|
||||
};
|
||||
|
||||
const { isHovered, setIsHovered, handleClick, hasClickableItems } =
|
||||
useGaugeChartHandlers({ data, onGaugeClick });
|
||||
const hasClickableItems = isDefined(onGaugeClick);
|
||||
|
||||
const {
|
||||
colorScheme,
|
||||
normalizedValue,
|
||||
clampedNormalizedValue,
|
||||
chartData,
|
||||
gradientId,
|
||||
defs,
|
||||
} = useGaugeChartData({
|
||||
data,
|
||||
colorRegistry,
|
||||
id,
|
||||
instanceId,
|
||||
isHovered,
|
||||
});
|
||||
const { colorScheme, normalizedValue, clampedNormalizedValue, chartData } =
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry,
|
||||
});
|
||||
|
||||
const { createTooltipData } = useGaugeChartTooltip({
|
||||
value: data.value,
|
||||
@@ -133,23 +123,13 @@ export const GraphWidgetGaugeChart = ({
|
||||
endAngle={90}
|
||||
innerRadius={0.7}
|
||||
padding={0.2}
|
||||
colors={[`url(#${gradientId})`, theme.background.tertiary]}
|
||||
defs={defs}
|
||||
fill={[
|
||||
{
|
||||
match: (d: { x: string }) => d.x === 'value',
|
||||
id: gradientId,
|
||||
},
|
||||
]}
|
||||
enableTracks={false}
|
||||
enableRadialGrid={false}
|
||||
enableCircularGrid={false}
|
||||
enableLabels={false}
|
||||
isInteractive={true}
|
||||
tooltip={renderTooltip}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onClick={hasClickableItems ? handleClick : undefined}
|
||||
layers={['bars', renderValueEndLine]}
|
||||
/>
|
||||
{showValue && (
|
||||
|
||||
+5
-122
@@ -7,10 +7,6 @@ describe('useGaugeChartData', () => {
|
||||
const mockColorRegistry: GraphColorRegistry = {
|
||||
blue: {
|
||||
name: 'blue',
|
||||
gradient: {
|
||||
normal: ['blue1', 'blue2'],
|
||||
hover: ['blue3', 'blue4'],
|
||||
},
|
||||
solid: 'blueSolid',
|
||||
variations: [
|
||||
'blue1',
|
||||
@@ -29,10 +25,6 @@ describe('useGaugeChartData', () => {
|
||||
},
|
||||
green: {
|
||||
name: 'green',
|
||||
gradient: {
|
||||
normal: ['green1', 'green2'],
|
||||
hover: ['green3', 'green4'],
|
||||
},
|
||||
solid: 'greenSolid',
|
||||
variations: [
|
||||
'green1',
|
||||
@@ -62,9 +54,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -83,9 +72,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -104,9 +90,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -125,9 +108,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -146,9 +126,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -168,9 +145,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -188,9 +162,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -208,9 +179,6 @@ describe('useGaugeChartData', () => {
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -225,84 +193,6 @@ describe('useGaugeChartData', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate gradient with correct angle', () => {
|
||||
const data: GaugeChartData = {
|
||||
value: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.defs[0].id).toBe(
|
||||
'gaugeGradient-test-gauge-instance-1',
|
||||
);
|
||||
const expectedAngle = -45;
|
||||
const expectedRadians = (expectedAngle * Math.PI) / 180 + Math.PI / 2;
|
||||
const expectedSin = Math.sin(expectedRadians);
|
||||
const expectedCos = -Math.cos(expectedRadians);
|
||||
const expectedX1 = 50 - expectedSin * 50;
|
||||
const expectedY1 = 50 - expectedCos * 50;
|
||||
|
||||
const actualX1 = parseFloat(result.current.defs[0].x1);
|
||||
const actualY1 = parseFloat(result.current.defs[0].y1);
|
||||
expect(actualX1).toBeCloseTo(expectedX1, 5);
|
||||
expect(actualY1).toBeCloseTo(expectedY1, 5);
|
||||
});
|
||||
|
||||
it('should handle hover state', () => {
|
||||
const data: GaugeChartData = {
|
||||
value: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.defs[0].colors).toEqual([
|
||||
{ offset: 0, color: 'blue3' },
|
||||
{ offset: 100, color: 'blue4' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate unique gradient id', () => {
|
||||
const data: GaugeChartData = {
|
||||
value: 50,
|
||||
min: 0,
|
||||
max: 100,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'unique-id',
|
||||
instanceId: 'unique-instance',
|
||||
isHovered: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.gradientId).toBe(
|
||||
'gaugeGradient-unique-id-unique-instance',
|
||||
);
|
||||
});
|
||||
|
||||
it('should memoize calculations', () => {
|
||||
const data: GaugeChartData = {
|
||||
value: 50,
|
||||
@@ -310,23 +200,16 @@ describe('useGaugeChartData', () => {
|
||||
max: 100,
|
||||
};
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ isHovered }) =>
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-gauge',
|
||||
instanceId: 'instance-1',
|
||||
isHovered,
|
||||
}),
|
||||
{ initialProps: { isHovered: false } },
|
||||
const { result } = renderHook(() =>
|
||||
useGaugeChartData({
|
||||
data,
|
||||
colorRegistry: mockColorRegistry,
|
||||
}),
|
||||
);
|
||||
|
||||
const firstColorScheme = result.current.colorScheme;
|
||||
const firstChartData = result.current.chartData;
|
||||
|
||||
rerender({ isHovered: false });
|
||||
|
||||
expect(result.current.colorScheme).toBe(firstColorScheme);
|
||||
expect(result.current.chartData).toBe(firstChartData);
|
||||
});
|
||||
|
||||
-17
@@ -1,23 +1,16 @@
|
||||
import { type GaugeChartData } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData';
|
||||
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
|
||||
import { createGradientDef } from '@/page-layout/widgets/graph/utils/createGradientDef';
|
||||
import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type UseGaugeChartDataProps = {
|
||||
data: GaugeChartData;
|
||||
colorRegistry: GraphColorRegistry;
|
||||
id: string;
|
||||
instanceId: string;
|
||||
isHovered: boolean;
|
||||
};
|
||||
|
||||
export const useGaugeChartData = ({
|
||||
data,
|
||||
colorRegistry,
|
||||
id,
|
||||
instanceId,
|
||||
isHovered,
|
||||
}: UseGaugeChartDataProps) => {
|
||||
const { value, min, max, color = 'blue' } = data;
|
||||
|
||||
@@ -53,20 +46,10 @@ export const useGaugeChartData = ({
|
||||
[clampedNormalizedValue],
|
||||
);
|
||||
|
||||
const gradientId = `gaugeGradient-${id}-${instanceId}`;
|
||||
const gaugeAngle = -90 + (clampedNormalizedValue / 100) * 90;
|
||||
|
||||
const defs = useMemo(
|
||||
() => [createGradientDef(colorScheme, gradientId, isHovered, gaugeAngle)],
|
||||
[colorScheme, gradientId, isHovered, gaugeAngle],
|
||||
);
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
normalizedValue,
|
||||
clampedNormalizedValue,
|
||||
chartData,
|
||||
gradientId,
|
||||
defs,
|
||||
};
|
||||
};
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import { type GaugeChartData } from '@/page-layout/widgets/graph/graphWidgetGaugeChart/types/GaugeChartData';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type UseGaugeChartHandlersProps = {
|
||||
data: GaugeChartData;
|
||||
onGaugeClick?: (data: GaugeChartData) => void;
|
||||
};
|
||||
|
||||
export const useGaugeChartHandlers = ({
|
||||
data,
|
||||
onGaugeClick,
|
||||
}: UseGaugeChartHandlersProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
onGaugeClick?.(data);
|
||||
};
|
||||
|
||||
const hasClickableItems = isDefined(onGaugeClick);
|
||||
|
||||
return {
|
||||
isHovered,
|
||||
setIsHovered,
|
||||
handleClick,
|
||||
hasClickableItems,
|
||||
};
|
||||
};
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import { LineAnimatedAreaPath } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/LineAnimatedAreaPath';
|
||||
import { LineAreaGradientDefs } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/LineAreaGradientDefs';
|
||||
import { type LineChartEnrichedSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartEnrichedSeries';
|
||||
import { computeLineAreaPath } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/computeLineAreaPath';
|
||||
import {
|
||||
type ComputedSeries,
|
||||
type LineCustomSvgLayerProps,
|
||||
type LineSeries,
|
||||
} from '@nivo/line';
|
||||
import { isNumberOrNaN } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type CustomStackedAreasLayerProps = {
|
||||
series: readonly ComputedSeries<LineSeries>[];
|
||||
innerHeight: number;
|
||||
enrichedSeries: LineChartEnrichedSeries[];
|
||||
enableArea: boolean;
|
||||
yScale: LineCustomSvgLayerProps<LineSeries>['yScale'];
|
||||
isStacked: boolean;
|
||||
};
|
||||
|
||||
type AreaPathData = {
|
||||
id: string;
|
||||
path: string;
|
||||
fillId: string;
|
||||
};
|
||||
|
||||
export const CustomStackedAreasLayer = ({
|
||||
series,
|
||||
innerHeight,
|
||||
enrichedSeries,
|
||||
enableArea,
|
||||
yScale,
|
||||
isStacked,
|
||||
}: CustomStackedAreasLayerProps) => {
|
||||
const seriesById = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
enrichedSeries.map((seriesItem) => [String(seriesItem.id), seriesItem]),
|
||||
),
|
||||
[enrichedSeries],
|
||||
);
|
||||
|
||||
const baseline = useMemo(() => {
|
||||
const scaled = yScale(0);
|
||||
if (!isNumberOrNaN(scaled)) {
|
||||
return innerHeight;
|
||||
}
|
||||
return Math.max(0, Math.min(scaled, innerHeight));
|
||||
}, [innerHeight, yScale]);
|
||||
|
||||
const paths = useMemo(() => {
|
||||
if (!enableArea) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const initialAreaPathData: AreaPathData[] = [];
|
||||
|
||||
return series.reduce((acc, currentSeries, index) => {
|
||||
const previousStackedSeries =
|
||||
isStacked && index > 0 ? series[index - 1] : null;
|
||||
|
||||
const enriched = seriesById.get(String(currentSeries.id));
|
||||
if (!isDefined(enriched)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const path = computeLineAreaPath({
|
||||
currentSeries,
|
||||
previousStackedSeries,
|
||||
baseline,
|
||||
});
|
||||
|
||||
if (isDefined(path)) {
|
||||
acc.push({
|
||||
id: String(currentSeries.id),
|
||||
path,
|
||||
fillId: enriched.areaFillId,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, initialAreaPathData);
|
||||
}, [enableArea, series, seriesById, baseline, isStacked]);
|
||||
|
||||
if (!enableArea) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<g>
|
||||
<LineAreaGradientDefs enrichedSeries={enrichedSeries} />
|
||||
{paths.map(({ id, path, fillId }) => (
|
||||
<LineAnimatedAreaPath key={id} path={path} fillId={fillId} />
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
+20
-19
@@ -5,6 +5,7 @@ import {
|
||||
type SliceHoverData,
|
||||
} from '@/page-layout/widgets/graph/graphWidgetLineChart/components/CustomCrosshairLayer';
|
||||
import { CustomPointLabelsLayer } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/CustomPointLabelsLayer';
|
||||
import { CustomStackedAreasLayer } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/CustomStackedAreasLayer';
|
||||
import { GraphLineChartTooltip } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/GraphLineChartTooltip';
|
||||
import { LINE_CHART_MARGIN_BOTTOM } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginBottom';
|
||||
import { LINE_CHART_MARGIN_LEFT } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginLeft';
|
||||
@@ -34,12 +35,13 @@ import {
|
||||
type Point,
|
||||
type SliceTooltipProps,
|
||||
} from '@nivo/line';
|
||||
import { useCallback, useId, useRef, useState } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
type CrosshairLayerProps = LineCustomSvgLayerProps<LineSeries>;
|
||||
type PointLabelsLayerProps = LineCustomSvgLayerProps<LineSeries>;
|
||||
type StackedAreasLayerProps = LineCustomSvgLayerProps<LineSeries>;
|
||||
|
||||
type GraphWidgetLineChartProps = {
|
||||
data: LineChartSeries[];
|
||||
@@ -87,7 +89,6 @@ export const GraphWidgetLineChart = ({
|
||||
onSliceClick,
|
||||
}: GraphWidgetLineChartProps) => {
|
||||
const theme = useTheme();
|
||||
const instanceId = useId();
|
||||
const colorRegistry = createGraphColorRegistry(theme);
|
||||
const chartTheme = useLineChartTheme();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -105,15 +106,11 @@ export const GraphWidgetLineChart = ({
|
||||
const effectiveMinimumValue = rangeMin ?? calculatedValueRange.minimum;
|
||||
const effectiveMaximumValue = rangeMax ?? calculatedValueRange.maximum;
|
||||
|
||||
const { enrichedSeries, nivoData, defs, fill, colors, legendItems } =
|
||||
useLineChartData({
|
||||
data,
|
||||
colorRegistry,
|
||||
id,
|
||||
instanceId,
|
||||
enableArea,
|
||||
theme,
|
||||
});
|
||||
const { enrichedSeries, nivoData, colors, legendItems } = useLineChartData({
|
||||
data,
|
||||
colorRegistry,
|
||||
id,
|
||||
});
|
||||
|
||||
const hasClickableItems = isDefined(onSliceClick);
|
||||
|
||||
@@ -194,6 +191,17 @@ export const GraphWidgetLineChart = ({
|
||||
/>
|
||||
);
|
||||
|
||||
const StackedAreasLayer = (layerProps: StackedAreasLayerProps) => (
|
||||
<CustomStackedAreasLayer
|
||||
series={layerProps.series}
|
||||
innerHeight={layerProps.innerHeight}
|
||||
enrichedSeries={enrichedSeries}
|
||||
enableArea={enableArea}
|
||||
yScale={layerProps.yScale}
|
||||
isStacked={groupMode === 'stacked'}
|
||||
/>
|
||||
);
|
||||
|
||||
const axisBottomConfig = getLineChartAxisBottomConfig(
|
||||
xAxisLabel,
|
||||
chartWidth,
|
||||
@@ -232,16 +240,11 @@ export const GraphWidgetLineChart = ({
|
||||
}}
|
||||
curve={'monotoneX'}
|
||||
lineWidth={1}
|
||||
enableArea={enableArea}
|
||||
areaBaselineValue={0}
|
||||
enablePoints={true}
|
||||
pointSize={0}
|
||||
enablePointLabel={false}
|
||||
pointBorderWidth={0}
|
||||
colors={colors}
|
||||
areaBlendMode={'normal'}
|
||||
defs={defs}
|
||||
fill={fill}
|
||||
axisTop={null}
|
||||
axisRight={null}
|
||||
axisBottom={axisBottomConfig}
|
||||
@@ -255,15 +258,13 @@ export const GraphWidgetLineChart = ({
|
||||
'grid',
|
||||
'markers',
|
||||
'axes',
|
||||
'areas',
|
||||
StackedAreasLayer,
|
||||
'lines',
|
||||
CrosshairLayer,
|
||||
'points',
|
||||
PointLabelsLayer,
|
||||
'legends',
|
||||
]}
|
||||
useMesh={true}
|
||||
crosshairType="cross"
|
||||
theme={chartTheme}
|
||||
/>
|
||||
</GraphWidgetChartContainer>
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { useMotionConfig } from '@nivo/core';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
|
||||
type LineAnimatedAreaPathProps = {
|
||||
path: string;
|
||||
fillId: string;
|
||||
};
|
||||
|
||||
export const LineAnimatedAreaPath = ({
|
||||
path,
|
||||
fillId,
|
||||
}: LineAnimatedAreaPathProps) => {
|
||||
const { animate, config: motionConfig } = useMotionConfig();
|
||||
const spring = useSpring({
|
||||
d: path,
|
||||
config: motionConfig,
|
||||
immediate: !animate,
|
||||
});
|
||||
|
||||
return (
|
||||
<animated.path d={spring.d} fill={`url(#${fillId})`} strokeWidth={0} />
|
||||
);
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { type LineChartEnrichedSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartEnrichedSeries';
|
||||
import { createAreaFillDef } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/createAreaFillDef';
|
||||
|
||||
type LineAreaGradientDefsProps = {
|
||||
enrichedSeries: LineChartEnrichedSeries[];
|
||||
};
|
||||
|
||||
export const LineAreaGradientDefs = ({
|
||||
enrichedSeries,
|
||||
}: LineAreaGradientDefsProps) => {
|
||||
return (
|
||||
<defs>
|
||||
{enrichedSeries.map((seriesItem) => {
|
||||
const def = createAreaFillDef(
|
||||
seriesItem.colorScheme,
|
||||
seriesItem.areaFillId,
|
||||
);
|
||||
return (
|
||||
<linearGradient
|
||||
key={def.id}
|
||||
id={def.id}
|
||||
x1={def.x1}
|
||||
y1={def.y1}
|
||||
x2={def.x2}
|
||||
y2={def.y2}
|
||||
>
|
||||
{def.colors.map((color, idx) => (
|
||||
<stop
|
||||
key={idx}
|
||||
offset={`${color.offset}%`}
|
||||
stopColor={color.color}
|
||||
stopOpacity={color.opacity}
|
||||
/>
|
||||
))}
|
||||
</linearGradient>
|
||||
);
|
||||
})}
|
||||
</defs>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const LINE_AREA_FILL_END_OPACITY = 0.01;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const LINE_AREA_FILL_START_OPACITY = 0.18;
|
||||
+6
-166
@@ -1,7 +1,6 @@
|
||||
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
|
||||
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { type ThemeType } from 'twenty-ui/theme';
|
||||
|
||||
import { useLineChartData } from '../useLineChartData';
|
||||
|
||||
@@ -9,10 +8,6 @@ describe('useLineChartData', () => {
|
||||
const mockColorRegistry: GraphColorRegistry = {
|
||||
red: {
|
||||
name: 'red',
|
||||
gradient: {
|
||||
normal: ['red1', 'red2'],
|
||||
hover: ['red3', 'red4'],
|
||||
},
|
||||
solid: 'redSolid',
|
||||
variations: [
|
||||
'red1',
|
||||
@@ -31,10 +26,6 @@ describe('useLineChartData', () => {
|
||||
},
|
||||
blue: {
|
||||
name: 'blue',
|
||||
gradient: {
|
||||
normal: ['blue1', 'blue2'],
|
||||
hover: ['blue3', 'blue4'],
|
||||
},
|
||||
solid: 'blueSolid',
|
||||
variations: [
|
||||
'blue1',
|
||||
@@ -53,8 +44,6 @@ describe('useLineChartData', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const mockTheme = { name: 'light' } as ThemeType;
|
||||
|
||||
const mockData: LineChartSeries[] = [
|
||||
{
|
||||
id: 'series1',
|
||||
@@ -77,61 +66,24 @@ describe('useLineChartData', () => {
|
||||
},
|
||||
];
|
||||
|
||||
it('should enrich series with color schemes', () => {
|
||||
it('should enrich series with color schemes and area fill ids', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: mockData,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.enrichedSeries[0].gradientId).toBe(
|
||||
'lineGradient-test-chart-instance-1-series1-0',
|
||||
);
|
||||
expect(result.current.enrichedSeries[0].label).toBe('Sales');
|
||||
expect(result.current.enrichedSeries[0].areaFillId).toBe(
|
||||
'areaFill-test-chart-series1-0',
|
||||
);
|
||||
|
||||
expect(result.current.enrichedSeries[1].label).toBe('Costs');
|
||||
});
|
||||
|
||||
it('should handle enableArea per series', () => {
|
||||
const dataWithArea: LineChartSeries[] = [
|
||||
{ ...mockData[0], enableArea: true },
|
||||
{ ...mockData[1], enableArea: false },
|
||||
];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: dataWithArea,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: true,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
expect(result.current.enrichedSeries[1].areaFillId).toBe(
|
||||
'areaFill-test-chart-series2-1',
|
||||
);
|
||||
|
||||
expect(result.current.enrichedSeries[0].shouldEnableArea).toBe(true);
|
||||
expect(result.current.enrichedSeries[1].shouldEnableArea).toBe(false);
|
||||
});
|
||||
|
||||
it('should use global enableArea when series does not specify', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: mockData,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: true,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.enrichedSeries[0].shouldEnableArea).toBe(true);
|
||||
expect(result.current.enrichedSeries[1].shouldEnableArea).toBe(true);
|
||||
});
|
||||
|
||||
it('should format data for Nivo', () => {
|
||||
@@ -140,9 +92,6 @@ describe('useLineChartData', () => {
|
||||
data: mockData,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -166,113 +115,12 @@ describe('useLineChartData', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate defs only for series with area enabled', () => {
|
||||
const dataWithArea: LineChartSeries[] = [
|
||||
{ ...mockData[0], enableArea: true },
|
||||
{ ...mockData[1], enableArea: false },
|
||||
];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: dataWithArea,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.defs).toHaveLength(1);
|
||||
expect(result.current.defs[0].id).toBe(
|
||||
'lineGradient-test-chart-instance-1-series1-0',
|
||||
);
|
||||
});
|
||||
|
||||
it('should reverse gradient for light theme', () => {
|
||||
const dataWithArea: LineChartSeries[] = [
|
||||
{ ...mockData[0], enableArea: true },
|
||||
];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: dataWithArea,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.defs[0].colors).toEqual([
|
||||
{ offset: 0, color: 'red2' },
|
||||
{ offset: 100, color: 'red1' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not reverse gradient for dark theme', () => {
|
||||
const dataWithArea: LineChartSeries[] = [
|
||||
{ ...mockData[0], enableArea: true },
|
||||
];
|
||||
|
||||
const darkTheme = { name: 'dark' } as ThemeType;
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: dataWithArea,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: darkTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.defs[0].colors).toEqual([
|
||||
{ offset: 0, color: 'red1' },
|
||||
{ offset: 100, color: 'red2' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should generate fill configuration', () => {
|
||||
const dataWithArea: LineChartSeries[] = [
|
||||
{ ...mockData[0], enableArea: true },
|
||||
{ ...mockData[1], enableArea: true },
|
||||
];
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: dataWithArea,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.fill).toEqual([
|
||||
{
|
||||
match: { id: 'series1' },
|
||||
id: 'lineGradient-test-chart-instance-1-series1-0',
|
||||
},
|
||||
{
|
||||
match: { id: 'series2' },
|
||||
id: 'lineGradient-test-chart-instance-1-series2-1',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should calculate legend items with totals', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useLineChartData({
|
||||
data: mockData,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -296,16 +144,11 @@ describe('useLineChartData', () => {
|
||||
data: [],
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.enrichedSeries).toEqual([]);
|
||||
expect(result.current.nivoData).toEqual([]);
|
||||
expect(result.current.defs).toEqual([]);
|
||||
expect(result.current.fill).toEqual([]);
|
||||
expect(result.current.colors).toEqual([]);
|
||||
expect(result.current.legendItems).toEqual([]);
|
||||
});
|
||||
@@ -323,9 +166,6 @@ describe('useLineChartData', () => {
|
||||
data: dataWithoutLabel,
|
||||
colorRegistry: mockColorRegistry,
|
||||
id: 'test-chart',
|
||||
instanceId: 'instance-1',
|
||||
enableArea: false,
|
||||
theme: mockTheme,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
+22
-65
@@ -1,93 +1,50 @@
|
||||
import { type LineChartEnrichedSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartEnrichedSeries';
|
||||
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
|
||||
import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry';
|
||||
import { createGradientDef } from '@/page-layout/widgets/graph/utils/createGradientDef';
|
||||
import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme';
|
||||
import { type LineSeries } from '@nivo/line';
|
||||
import { useMemo } from 'react';
|
||||
import { type ThemeType } from 'twenty-ui/theme';
|
||||
|
||||
type UseLineChartDataProps = {
|
||||
data: LineChartSeries[];
|
||||
colorRegistry: GraphColorRegistry;
|
||||
id: string;
|
||||
instanceId: string;
|
||||
enableArea: boolean;
|
||||
theme: ThemeType;
|
||||
};
|
||||
|
||||
export const useLineChartData = ({
|
||||
data,
|
||||
colorRegistry,
|
||||
id,
|
||||
instanceId,
|
||||
enableArea,
|
||||
theme,
|
||||
}: UseLineChartDataProps) => {
|
||||
const enrichedSeries = useMemo((): LineChartEnrichedSeries[] => {
|
||||
return data.map((series, index) => {
|
||||
return useMemo(() => {
|
||||
const enrichedSeries: LineChartEnrichedSeries[] = [];
|
||||
const nivoData: LineSeries[] = [];
|
||||
const colors: string[] = [];
|
||||
const legendItems: { id: string; label: string; color: string }[] = [];
|
||||
|
||||
for (const [index, series] of data.entries()) {
|
||||
const colorScheme = getColorScheme({
|
||||
registry: colorRegistry,
|
||||
colorName: series.color,
|
||||
fallbackIndex: index,
|
||||
totalGroups: data.length,
|
||||
});
|
||||
const shouldEnableArea = series.enableArea ?? enableArea;
|
||||
const gradientId = `lineGradient-${id}-${instanceId}-${series.id}-${index}`;
|
||||
|
||||
return {
|
||||
...series,
|
||||
colorScheme,
|
||||
gradientId,
|
||||
shouldEnableArea,
|
||||
label: series.label || series.id,
|
||||
};
|
||||
});
|
||||
}, [data, colorRegistry, id, instanceId, enableArea]);
|
||||
const sanitizedSeriesId = series.id
|
||||
.replace(/\s+/g, '_')
|
||||
.replace(/[^a-zA-Z0-9_-]/g, '');
|
||||
const areaFillId = `areaFill-${id}-${sanitizedSeriesId}-${index}`;
|
||||
const label = series.label || series.id;
|
||||
|
||||
const nivoData: LineSeries[] = data.map((series) => ({
|
||||
id: series.id,
|
||||
data: series.data.map((point) => ({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
})),
|
||||
}));
|
||||
enrichedSeries.push({ ...series, colorScheme, areaFillId, label });
|
||||
nivoData.push({
|
||||
id: series.id,
|
||||
data: series.data.map((point) => ({ x: point.x, y: point.y })),
|
||||
});
|
||||
colors.push(colorScheme.solid);
|
||||
legendItems.push({ id: series.id, label, color: colorScheme.solid });
|
||||
}
|
||||
|
||||
const seriesWithArea = enrichedSeries.filter(
|
||||
(series) => series.shouldEnableArea,
|
||||
);
|
||||
|
||||
const defs = seriesWithArea.map((series) =>
|
||||
createGradientDef(
|
||||
series.colorScheme,
|
||||
series.gradientId,
|
||||
false,
|
||||
90,
|
||||
theme.name === 'light',
|
||||
),
|
||||
);
|
||||
|
||||
const fill = seriesWithArea.map((series) => ({
|
||||
match: { id: series.id },
|
||||
id: series.gradientId,
|
||||
}));
|
||||
|
||||
const colors = enrichedSeries.map((series) => series.colorScheme.solid);
|
||||
|
||||
const legendItems = enrichedSeries.map((series) => {
|
||||
return {
|
||||
id: series.id,
|
||||
label: series.label,
|
||||
color: series.colorScheme.solid,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
enrichedSeries,
|
||||
nivoData,
|
||||
defs,
|
||||
fill,
|
||||
colors,
|
||||
legendItems,
|
||||
};
|
||||
return { enrichedSeries, nivoData, colors, legendItems };
|
||||
}, [data, colorRegistry, id]);
|
||||
};
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@ import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphCo
|
||||
|
||||
export type LineChartEnrichedSeries = LineChartSeries & {
|
||||
colorScheme: GraphColorScheme;
|
||||
gradientId: string;
|
||||
shouldEnableArea: boolean;
|
||||
areaFillId: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
-1
@@ -6,5 +6,4 @@ export type LineChartSeries = {
|
||||
label?: string;
|
||||
color?: GraphColor;
|
||||
data: LineChartDataPoint[];
|
||||
enableArea?: boolean;
|
||||
};
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { type ComputedSeries, type LineSeries } from '@nivo/line';
|
||||
|
||||
jest.mock('d3-shape', () => {
|
||||
const createAreaGenerator = () => {
|
||||
let y0Fn: (d: any, i: number) => number = () => 0;
|
||||
|
||||
const generator = ((data: any[]) =>
|
||||
data
|
||||
.map((d, i) => `(${d.position.x},${y0Fn(d, i)},${d.position.y})`)
|
||||
.join('|')) as any;
|
||||
|
||||
generator.defined = () => generator;
|
||||
generator.x = () => generator;
|
||||
generator.y1 = () => generator;
|
||||
generator.y0 = (fn: (d: any, i: number) => number) => {
|
||||
y0Fn = fn;
|
||||
return generator;
|
||||
};
|
||||
generator.curve = () => generator;
|
||||
|
||||
return generator;
|
||||
};
|
||||
|
||||
return {
|
||||
area: createAreaGenerator,
|
||||
curveMonotoneX: () => 'mockCurve',
|
||||
};
|
||||
});
|
||||
|
||||
import { computeLineAreaPath } from '../computeLineAreaPath';
|
||||
|
||||
const buildComputedSeries = (
|
||||
points: { x: number; y: number }[],
|
||||
): ComputedSeries<LineSeries> =>
|
||||
({
|
||||
id: 'series',
|
||||
data: points.map(({ x, y }) => ({
|
||||
data: { x, y },
|
||||
position: { x, y },
|
||||
})),
|
||||
}) as unknown as ComputedSeries<LineSeries>;
|
||||
|
||||
describe('computeLineAreaPath', () => {
|
||||
it('fills to the provided baseline for non-stacked mode', () => {
|
||||
const currentSeries = buildComputedSeries([
|
||||
{ x: 0, y: 10 },
|
||||
{ x: 10, y: 20 },
|
||||
]);
|
||||
|
||||
const path = computeLineAreaPath({
|
||||
currentSeries,
|
||||
baseline: 100,
|
||||
});
|
||||
|
||||
expect(path).toBe('(0,100,10)|(10,100,20)');
|
||||
});
|
||||
|
||||
it('fills between current and previous series for stacked mode', () => {
|
||||
const currentSeries = buildComputedSeries([
|
||||
{ x: 0, y: 10 },
|
||||
{ x: 10, y: 20 },
|
||||
]);
|
||||
const previousStackedSeries = buildComputedSeries([
|
||||
{ x: 0, y: 5 },
|
||||
{ x: 10, y: 15 },
|
||||
]);
|
||||
|
||||
const path = computeLineAreaPath({
|
||||
currentSeries,
|
||||
previousStackedSeries,
|
||||
baseline: 100,
|
||||
});
|
||||
|
||||
expect(path).toBe('(0,5,10)|(10,15,20)');
|
||||
});
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { area, curveMonotoneX, type CurveFactory } from 'd3-shape';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ComputedSeries, type LineSeries } from '@nivo/line';
|
||||
|
||||
type ComputeLineAreaPathParams = {
|
||||
currentSeries: ComputedSeries<LineSeries>;
|
||||
previousStackedSeries?: ComputedSeries<LineSeries> | null;
|
||||
baseline: number;
|
||||
curve?: CurveFactory;
|
||||
};
|
||||
|
||||
export const computeLineAreaPath = ({
|
||||
currentSeries,
|
||||
previousStackedSeries,
|
||||
baseline,
|
||||
curve = curveMonotoneX,
|
||||
}: ComputeLineAreaPathParams) => {
|
||||
type PositionData = (typeof currentSeries.data)[number];
|
||||
|
||||
const areaGenerator = area<PositionData>()
|
||||
.defined((d) => d.position.x !== null && d.position.y !== null)
|
||||
.x((d) => d.position.x ?? 0)
|
||||
.y1((d) => d.position.y ?? 0)
|
||||
.y0((_, index) => {
|
||||
if (isDefined(previousStackedSeries)) {
|
||||
const previousPoint = previousStackedSeries.data[index];
|
||||
if (isDefined(previousPoint) && isDefined(previousPoint.position.y)) {
|
||||
return previousPoint.position.y;
|
||||
}
|
||||
}
|
||||
return baseline;
|
||||
})
|
||||
.curve(curve);
|
||||
|
||||
return areaGenerator(currentSeries.data);
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { LINE_AREA_FILL_END_OPACITY } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineAreaFillEndOpacity';
|
||||
import { LINE_AREA_FILL_START_OPACITY } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineAreaFillStartOpacity';
|
||||
import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphColorScheme';
|
||||
|
||||
export const createAreaFillDef = (
|
||||
colorScheme: GraphColorScheme,
|
||||
id: string,
|
||||
) => {
|
||||
return {
|
||||
id,
|
||||
type: 'linearGradient' as const,
|
||||
x1: '0%',
|
||||
y1: '0%',
|
||||
x2: '0%',
|
||||
y2: '100%',
|
||||
colors: [
|
||||
{
|
||||
offset: 0,
|
||||
color: colorScheme.solid,
|
||||
opacity: LINE_AREA_FILL_START_OPACITY,
|
||||
},
|
||||
{
|
||||
offset: 100,
|
||||
color: colorScheme.solid,
|
||||
opacity: LINE_AREA_FILL_END_OPACITY,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
-8
@@ -12,10 +12,6 @@ describe('usePieChartData', () => {
|
||||
const mockColorRegistry: GraphColorRegistry = {
|
||||
red: {
|
||||
name: 'red',
|
||||
gradient: {
|
||||
normal: ['red1', 'red2'],
|
||||
hover: ['red3', 'red4'],
|
||||
},
|
||||
solid: 'redSolid',
|
||||
variations: [
|
||||
'red1',
|
||||
@@ -34,10 +30,6 @@ describe('usePieChartData', () => {
|
||||
},
|
||||
blue: {
|
||||
name: 'blue',
|
||||
gradient: {
|
||||
normal: ['blue1', 'blue2'],
|
||||
hover: ['blue3', 'blue4'],
|
||||
},
|
||||
solid: 'blueSolid',
|
||||
variations: [
|
||||
'blue1',
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
export type GraphColorScheme = {
|
||||
name: string;
|
||||
gradient: {
|
||||
normal: [string, string];
|
||||
hover: [string, string];
|
||||
};
|
||||
solid: string;
|
||||
variations: [
|
||||
string,
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
import { type GraphColorScheme } from '../types/GraphColorScheme';
|
||||
import { calculateAngularGradient } from './calculateAngularGradient';
|
||||
|
||||
export const createGradientDef = (
|
||||
colorScheme: GraphColorScheme,
|
||||
id: string,
|
||||
isHovered: boolean = false,
|
||||
angle?: number,
|
||||
reverseGradient: boolean = false,
|
||||
) => {
|
||||
const colors = isHovered
|
||||
? colorScheme.gradient.hover
|
||||
: colorScheme.gradient.normal;
|
||||
|
||||
const coords =
|
||||
angle !== undefined
|
||||
? calculateAngularGradient(angle)
|
||||
: { x1: '0%', y1: '0%', x2: '0%', y2: '100%' };
|
||||
|
||||
return {
|
||||
id,
|
||||
type: 'linearGradient' as const,
|
||||
...coords,
|
||||
colors: reverseGradient
|
||||
? [
|
||||
{ offset: 0, color: colors[1] },
|
||||
{ offset: 100, color: colors[0] },
|
||||
]
|
||||
: [
|
||||
{ offset: 0, color: colors[0] },
|
||||
{ offset: 100, color: colors[1] },
|
||||
],
|
||||
};
|
||||
};
|
||||
-100
@@ -7,10 +7,6 @@ export const createGraphColorRegistry = (
|
||||
): GraphColorRegistry => ({
|
||||
blue: {
|
||||
name: 'blue',
|
||||
gradient: {
|
||||
normal: [theme.color.blue3, theme.color.blue5],
|
||||
hover: [theme.color.blue7, theme.color.blue8],
|
||||
},
|
||||
solid: theme.color.blue8,
|
||||
variations: [
|
||||
theme.color.blue1,
|
||||
@@ -29,10 +25,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
purple: {
|
||||
name: 'purple',
|
||||
gradient: {
|
||||
normal: [theme.color.purple3, theme.color.purple5],
|
||||
hover: [theme.color.purple7, theme.color.purple8],
|
||||
},
|
||||
solid: theme.color.purple8,
|
||||
variations: [
|
||||
theme.color.purple1,
|
||||
@@ -51,10 +43,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
turquoise: {
|
||||
name: 'turquoise',
|
||||
gradient: {
|
||||
normal: [theme.color.turquoise3, theme.color.turquoise5],
|
||||
hover: [theme.color.turquoise7, theme.color.turquoise8],
|
||||
},
|
||||
solid: theme.color.turquoise8,
|
||||
variations: [
|
||||
theme.color.turquoise1,
|
||||
@@ -73,10 +61,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
orange: {
|
||||
name: 'orange',
|
||||
gradient: {
|
||||
normal: [theme.color.orange3, theme.color.orange5],
|
||||
hover: [theme.color.orange7, theme.color.orange8],
|
||||
},
|
||||
solid: theme.color.orange8,
|
||||
variations: [
|
||||
theme.color.orange1,
|
||||
@@ -95,10 +79,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
pink: {
|
||||
name: 'pink',
|
||||
gradient: {
|
||||
normal: [theme.color.pink3, theme.color.pink5],
|
||||
hover: [theme.color.pink7, theme.color.pink8],
|
||||
},
|
||||
solid: theme.color.pink8,
|
||||
variations: [
|
||||
theme.color.pink1,
|
||||
@@ -117,10 +97,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
yellow: {
|
||||
name: 'yellow',
|
||||
gradient: {
|
||||
normal: [theme.color.yellow3, theme.color.yellow5],
|
||||
hover: [theme.color.yellow7, theme.color.yellow8],
|
||||
},
|
||||
solid: theme.color.yellow8,
|
||||
variations: [
|
||||
theme.color.yellow1,
|
||||
@@ -139,10 +115,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
red: {
|
||||
name: 'red',
|
||||
gradient: {
|
||||
normal: [theme.color.red3, theme.color.red5],
|
||||
hover: [theme.color.red7, theme.color.red8],
|
||||
},
|
||||
solid: theme.color.red8,
|
||||
variations: [
|
||||
theme.color.red1,
|
||||
@@ -161,10 +133,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
green: {
|
||||
name: 'green',
|
||||
gradient: {
|
||||
normal: [theme.color.green3, theme.color.green5],
|
||||
hover: [theme.color.green7, theme.color.green8],
|
||||
},
|
||||
solid: theme.color.green8,
|
||||
variations: [
|
||||
theme.color.green1,
|
||||
@@ -183,10 +151,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
sky: {
|
||||
name: 'sky',
|
||||
gradient: {
|
||||
normal: [theme.color.sky3, theme.color.sky5],
|
||||
hover: [theme.color.sky7, theme.color.sky8],
|
||||
},
|
||||
solid: theme.color.sky8,
|
||||
variations: [
|
||||
theme.color.sky1,
|
||||
@@ -205,10 +169,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
gray: {
|
||||
name: 'gray',
|
||||
gradient: {
|
||||
normal: [theme.color.gray3, theme.color.gray5],
|
||||
hover: [theme.color.gray7, theme.color.gray8],
|
||||
},
|
||||
solid: theme.color.gray8,
|
||||
variations: [
|
||||
theme.color.gray1,
|
||||
@@ -227,10 +187,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
tomato: {
|
||||
name: 'tomato',
|
||||
gradient: {
|
||||
normal: [theme.color.tomato3, theme.color.tomato5],
|
||||
hover: [theme.color.tomato7, theme.color.tomato8],
|
||||
},
|
||||
solid: theme.color.tomato8,
|
||||
variations: [
|
||||
theme.color.tomato1,
|
||||
@@ -249,10 +205,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
ruby: {
|
||||
name: 'ruby',
|
||||
gradient: {
|
||||
normal: [theme.color.ruby3, theme.color.ruby5],
|
||||
hover: [theme.color.ruby7, theme.color.ruby8],
|
||||
},
|
||||
solid: theme.color.ruby8,
|
||||
variations: [
|
||||
theme.color.ruby1,
|
||||
@@ -271,10 +223,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
crimson: {
|
||||
name: 'crimson',
|
||||
gradient: {
|
||||
normal: [theme.color.crimson3, theme.color.crimson5],
|
||||
hover: [theme.color.crimson7, theme.color.crimson8],
|
||||
},
|
||||
solid: theme.color.crimson8,
|
||||
variations: [
|
||||
theme.color.crimson1,
|
||||
@@ -293,10 +241,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
plum: {
|
||||
name: 'plum',
|
||||
gradient: {
|
||||
normal: [theme.color.plum3, theme.color.plum5],
|
||||
hover: [theme.color.plum7, theme.color.plum8],
|
||||
},
|
||||
solid: theme.color.plum8,
|
||||
variations: [
|
||||
theme.color.plum1,
|
||||
@@ -315,10 +259,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
violet: {
|
||||
name: 'violet',
|
||||
gradient: {
|
||||
normal: [theme.color.violet3, theme.color.violet5],
|
||||
hover: [theme.color.violet7, theme.color.violet8],
|
||||
},
|
||||
solid: theme.color.violet8,
|
||||
variations: [
|
||||
theme.color.violet1,
|
||||
@@ -337,10 +277,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
iris: {
|
||||
name: 'iris',
|
||||
gradient: {
|
||||
normal: [theme.color.iris3, theme.color.iris5],
|
||||
hover: [theme.color.iris7, theme.color.iris8],
|
||||
},
|
||||
solid: theme.color.iris8,
|
||||
variations: [
|
||||
theme.color.iris1,
|
||||
@@ -359,10 +295,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
cyan: {
|
||||
name: 'cyan',
|
||||
gradient: {
|
||||
normal: [theme.color.cyan3, theme.color.cyan5],
|
||||
hover: [theme.color.cyan7, theme.color.cyan8],
|
||||
},
|
||||
solid: theme.color.cyan8,
|
||||
variations: [
|
||||
theme.color.cyan1,
|
||||
@@ -381,10 +313,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
jade: {
|
||||
name: 'jade',
|
||||
gradient: {
|
||||
normal: [theme.color.jade3, theme.color.jade5],
|
||||
hover: [theme.color.jade7, theme.color.jade8],
|
||||
},
|
||||
solid: theme.color.jade8,
|
||||
variations: [
|
||||
theme.color.jade1,
|
||||
@@ -403,10 +331,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
grass: {
|
||||
name: 'grass',
|
||||
gradient: {
|
||||
normal: [theme.color.grass3, theme.color.grass5],
|
||||
hover: [theme.color.grass7, theme.color.grass8],
|
||||
},
|
||||
solid: theme.color.grass8,
|
||||
variations: [
|
||||
theme.color.grass1,
|
||||
@@ -425,10 +349,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
mint: {
|
||||
name: 'mint',
|
||||
gradient: {
|
||||
normal: [theme.color.mint3, theme.color.mint5],
|
||||
hover: [theme.color.mint7, theme.color.mint8],
|
||||
},
|
||||
solid: theme.color.mint8,
|
||||
variations: [
|
||||
theme.color.mint1,
|
||||
@@ -447,10 +367,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
lime: {
|
||||
name: 'lime',
|
||||
gradient: {
|
||||
normal: [theme.color.lime3, theme.color.lime5],
|
||||
hover: [theme.color.lime7, theme.color.lime8],
|
||||
},
|
||||
solid: theme.color.lime8,
|
||||
variations: [
|
||||
theme.color.lime1,
|
||||
@@ -469,10 +385,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
bronze: {
|
||||
name: 'bronze',
|
||||
gradient: {
|
||||
normal: [theme.color.bronze3, theme.color.bronze5],
|
||||
hover: [theme.color.bronze7, theme.color.bronze8],
|
||||
},
|
||||
solid: theme.color.bronze8,
|
||||
variations: [
|
||||
theme.color.bronze1,
|
||||
@@ -491,10 +403,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
gold: {
|
||||
name: 'gold',
|
||||
gradient: {
|
||||
normal: [theme.color.gold3, theme.color.gold5],
|
||||
hover: [theme.color.gold7, theme.color.gold8],
|
||||
},
|
||||
solid: theme.color.gold8,
|
||||
variations: [
|
||||
theme.color.gold1,
|
||||
@@ -513,10 +421,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
brown: {
|
||||
name: 'brown',
|
||||
gradient: {
|
||||
normal: [theme.color.brown3, theme.color.brown5],
|
||||
hover: [theme.color.brown7, theme.color.brown8],
|
||||
},
|
||||
solid: theme.color.brown8,
|
||||
variations: [
|
||||
theme.color.brown1,
|
||||
@@ -535,10 +439,6 @@ export const createGraphColorRegistry = (
|
||||
},
|
||||
amber: {
|
||||
name: 'amber',
|
||||
gradient: {
|
||||
normal: [theme.color.amber3, theme.color.amber5],
|
||||
hover: [theme.color.amber7, theme.color.amber8],
|
||||
},
|
||||
solid: theme.color.amber8,
|
||||
variations: [
|
||||
theme.color.amber1,
|
||||
|
||||
Reference in New Issue
Block a user