diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx index 188da69a67..e1a4aed828 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/__stories__/GraphWidgetBarChart.stories.tsx @@ -1,6 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react'; import { GraphWidgetBarChart } from '@/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { CatalogDecorator, ComponentDecorator } from 'twenty-ui/testing'; const meta: Meta = { @@ -280,7 +281,7 @@ export const Horizontal: Story = { ], indexBy: 'product', keys: ['score'], - layout: 'horizontal', + layout: BarChartLayout.HORIZONTAL, showLegend: false, showGrid: true, xAxisLabel: 'Score', diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx index 1ebe4cd86d..2f4df8ac3b 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomBarItem.tsx @@ -1,5 +1,6 @@ import { BAR_CHART_HOVER_BRIGHTNESS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartHoverBrightness'; import { BAR_CHART_MAXIMUM_WIDTH } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/MaximumBarWidth'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarDatum, type BarItemProps } from '@nivo/bar'; import { animated, to } from '@react-spring/web'; import { isNumber } from '@sniptt/guards'; @@ -12,7 +13,7 @@ type CustomBarItemProps = BarItemProps & { groupMode?: 'grouped' | 'stacked'; data?: readonly D[]; indexBy?: string; - layout?: 'vertical' | 'horizontal'; + layout?: BarChartLayout; chartId?: string; }; @@ -46,7 +47,7 @@ export const CustomBarItem = ({ groupMode = 'grouped', data: chartData, indexBy, - layout = 'vertical', + layout = BarChartLayout.VERTICAL, chartId, }: CustomBarItemProps) => { const handleClick = useCallback( @@ -123,7 +124,7 @@ export const CustomBarItem = ({ barData.indexValue, ]); - const isHorizontal = layout === 'horizontal'; + const isHorizontal = layout === BarChartLayout.HORIZONTAL; const clipPathId = `round-corner-${chartId ?? 'chart'}-${barData.index}-${ seriesIndex >= 0 ? seriesIndex : 'x' }`; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx index a6dc850cfc..695566f2f2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/CustomTotalsLayer.tsx @@ -1,4 +1,5 @@ import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { useTheme } from '@emotion/react'; import { type BarCustomLayerProps, type ComputedBarDatum } from '@nivo/bar'; import { animated } from '@react-spring/web'; @@ -10,7 +11,7 @@ type CustomTotalsLayerProps = Pick< > & { formatValue?: (value: number) => string; offset?: number; - layout?: 'vertical' | 'horizontal'; + layout?: BarChartLayout; groupMode?: 'grouped' | 'stacked'; omitNullValues?: boolean; }; @@ -137,12 +138,12 @@ export const CustomTotalsLayer = ({ bars, formatValue, offset = 0, - layout = 'vertical', + layout = BarChartLayout.VERTICAL, groupMode = 'grouped', omitNullValues = false, }: CustomTotalsLayerProps) => { const theme = useTheme(); - const isVertical = layout === 'vertical'; + const isVertical = layout === BarChartLayout.VERTICAL; const labels = groupMode === 'stacked' diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx index 488e6c1bbd..d830c4e4c1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx @@ -7,6 +7,7 @@ import { BAR_CHART_MINIMUM_INNER_PADDING } from '@/page-layout/widgets/graph/gra import { useBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartData'; import { useBarChartTheme } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartTheme'; import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { calculateBarChartValueRange } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartValueRange'; import { calculateStackedBarChartValueRange } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateStackedBarChartValueRange'; @@ -45,7 +46,7 @@ type GraphWidgetBarChartProps = { xAxisLabel?: string; yAxisLabel?: string; id: string; - layout?: 'vertical' | 'horizontal'; + layout?: BarChartLayout; groupMode?: 'grouped' | 'stacked'; seriesLabels?: Record; rangeMin?: number; @@ -73,7 +74,7 @@ export const GraphWidgetBarChart = ({ xAxisLabel, yAxisLabel, id, - layout = 'vertical', + layout = BarChartLayout.VERTICAL, groupMode, seriesLabels, rangeMin, @@ -209,7 +210,7 @@ export const GraphWidgetBarChart = ({ const zeroMarker = hasNegativeValues ? [ { - axis: (layout === 'vertical' ? 'y' : 'x') as 'y' | 'x', + axis: (layout === BarChartLayout.VERTICAL ? 'y' : 'x') as 'y' | 'x', value: 0, lineStyle: { stroke: theme.border.color.medium, @@ -260,10 +261,10 @@ export const GraphWidgetBarChart = ({ axisRight={null} axisBottom={axisBottomConfig} axisLeft={axisLeftConfig} - enableGridX={layout === 'horizontal' && showGrid} - enableGridY={layout === 'vertical' && showGrid} - gridXValues={layout === 'horizontal' ? 5 : undefined} - gridYValues={layout === 'vertical' ? 5 : undefined} + enableGridX={layout === BarChartLayout.HORIZONTAL && showGrid} + enableGridY={layout === BarChartLayout.VERTICAL && showGrid} + gridXValues={layout === BarChartLayout.HORIZONTAL ? 5 : undefined} + gridYValues={layout === BarChartLayout.VERTICAL ? 5 : undefined} enableLabel={false} labelSkipWidth={12} innerPadding={ diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinTickSpacingHeightRatio.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinTickSpacingHeightRatio.ts new file mode 100644 index 0000000000..1c3940d81a --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinTickSpacingHeightRatio.ts @@ -0,0 +1 @@ +export const BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO = 2.5; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick.ts new file mode 100644 index 0000000000..bbeb0e84e4 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick.ts @@ -0,0 +1 @@ +export const BAR_CHART_MINIMUM_WIDTH_PER_TICK = 100; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts index 50c299f269..981d97481c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts @@ -1,5 +1,6 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { type BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery'; import { transformGroupByDataToBarChartData } from '@/page-layout/widgets/graph/utils/transformGroupByDataToBarChartData'; @@ -19,7 +20,7 @@ type UseGraphBarChartWidgetDataResult = { xAxisLabel?: string; yAxisLabel?: string; showDataLabels: boolean; - layout?: 'vertical' | 'horizontal'; + layout?: BarChartLayout; loading: boolean; error?: Error; hasTooManyGroups: boolean; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout.ts new file mode 100644 index 0000000000..ff637662cb --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout.ts @@ -0,0 +1,4 @@ +export enum BarChartLayout { + VERTICAL = 'vertical', + HORIZONTAL = 'horizontal', +} diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts index 06002cedb4..86d4bc71bb 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/calculateBarChartEndLineCoordinates.test.ts @@ -1,5 +1,6 @@ -import { type ComputedBarDatum } from '@nivo/bar'; import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; +import { type ComputedBarDatum } from '@nivo/bar'; import { calculateBarChartEndLineCoordinates } from '../calculateBarChartEndLineCoordinates'; describe('calculateBarChartEndLineCoordinates', () => { const createMockBar = ( @@ -26,7 +27,10 @@ describe('calculateBarChartEndLineCoordinates', () => { describe('vertical layout', () => { it('should calculate horizontal line coordinates at the top of vertical bars', () => { const mockBar = createMockBar(); - const result = calculateBarChartEndLineCoordinates(mockBar, 'vertical'); + const result = calculateBarChartEndLineCoordinates( + mockBar, + BarChartLayout.VERTICAL, + ); expect(result).toEqual({ x1: 100, x2: 140, @@ -38,7 +42,7 @@ describe('calculateBarChartEndLineCoordinates', () => { const barAtOrigin = createMockBar({ x: 0, y: 0 }); const result = calculateBarChartEndLineCoordinates( barAtOrigin, - 'vertical', + BarChartLayout.VERTICAL, ); expect(result).toEqual({ x1: 0, @@ -51,7 +55,7 @@ describe('calculateBarChartEndLineCoordinates', () => { const negativeBar = createMockBar({ x: -50, y: -20 }); const result = calculateBarChartEndLineCoordinates( negativeBar, - 'vertical', + BarChartLayout.VERTICAL, ); expect(result).toEqual({ x1: -50, @@ -64,7 +68,10 @@ describe('calculateBarChartEndLineCoordinates', () => { describe('horizontal layout', () => { it('should calculate vertical line coordinates at the end of horizontal bars', () => { const mockBar = createMockBar(); - const result = calculateBarChartEndLineCoordinates(mockBar, 'horizontal'); + const result = calculateBarChartEndLineCoordinates( + mockBar, + BarChartLayout.HORIZONTAL, + ); expect(result).toEqual({ x1: 140, x2: 140, @@ -74,7 +81,10 @@ describe('calculateBarChartEndLineCoordinates', () => { }); it('should handle bars with different dimensions', () => { const wideBar = createMockBar({ width: 100, height: 20 }); - const result = calculateBarChartEndLineCoordinates(wideBar, 'horizontal'); + const result = calculateBarChartEndLineCoordinates( + wideBar, + BarChartLayout.HORIZONTAL, + ); expect(result).toEqual({ x1: 200, x2: 200, @@ -84,7 +94,10 @@ describe('calculateBarChartEndLineCoordinates', () => { }); it('should handle very thin bars', () => { const thinBar = createMockBar({ width: 1, height: 200 }); - const result = calculateBarChartEndLineCoordinates(thinBar, 'horizontal'); + const result = calculateBarChartEndLineCoordinates( + thinBar, + BarChartLayout.HORIZONTAL, + ); expect(result).toEqual({ x1: 101, x2: 101, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts index 6cc8aefa1b..0ba1c3c1a4 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateBarChartEndLineCoordinates.ts @@ -1,11 +1,12 @@ import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type ComputedBarDatum } from '@nivo/bar'; export const calculateBarChartEndLineCoordinates = ( bar: ComputedBarDatum, - layout: 'vertical' | 'horizontal', + layout: BarChartLayout, ) => { - if (layout === 'vertical') { + if (layout === BarChartLayout.VERTICAL) { return { x1: bar.x, x2: bar.x + bar.width, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateMaxTickLabelLength.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateMaxTickLabelLength.ts new file mode 100644 index 0000000000..31fff8421b --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateMaxTickLabelLength.ts @@ -0,0 +1,15 @@ +const AVERAGE_CHARACTER_WIDTH_RATIO = 0.6; +const MIN_TICK_LABEL_LENGTH = 5; + +export const calculateMaxTickLabelLength = ({ + widthPerTick, + axisFontSize, +}: { + widthPerTick: number; + axisFontSize: number; +}): number => { + const averageCharacterWidth = axisFontSize * AVERAGE_CHARACTER_WIDTH_RATIO; + const calculatedLength = Math.floor(widthPerTick / averageCharacterWidth); + + return Math.max(MIN_TICK_LABEL_LENGTH, calculatedLength); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateWidthPerTick.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateWidthPerTick.ts new file mode 100644 index 0000000000..36bee52d1a --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateWidthPerTick.ts @@ -0,0 +1,19 @@ +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; + +export const calculateWidthPerTick = ({ + layout, + availableWidth, + categoryTickCount, + valueTickCount, +}: { + layout: BarChartLayout; + availableWidth: number; + categoryTickCount: number; + valueTickCount: number; +}): number => { + if (layout === BarChartLayout.VERTICAL) { + return categoryTickCount > 0 ? availableWidth / categoryTickCount : 0; + } + + return valueTickCount > 0 ? availableWidth / valueTickCount : 0; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts index 711e1f211e..5439574344 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues.ts @@ -1,30 +1,43 @@ +import { BAR_CHART_MINIMUM_WIDTH_PER_TICK } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick'; import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; +import { computeMinHeightPerTick } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick'; import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; -const MINIMUM_WIDTH_PER_TICK = 100; - export const computeBarChartCategoryTickValues = ({ - width, + axisSize, + axisFontSize, data, indexBy, xAxisLabel, yAxisLabel, layout, }: { - width: number; + axisSize: number; + axisFontSize: number; data: BarChartDataItem[]; indexBy: string; - layout: 'vertical' | 'horizontal'; + layout: BarChartLayout; xAxisLabel?: string; yAxisLabel?: string; }): (string | number)[] => { - if (width === 0 || data.length === 0) return []; + if (axisSize === 0 || data.length === 0) return []; const margins = getBarChartMargins({ xAxisLabel, yAxisLabel, layout }); - const horizontalMargins = margins.left + margins.right; - const availableWidth = width - horizontalMargins; - const numberOfTicks = Math.floor(availableWidth / MINIMUM_WIDTH_PER_TICK); + const totalMargins = + layout === BarChartLayout.VERTICAL + ? margins.left + margins.right + : margins.top + margins.bottom; + + const availableAxisSize = axisSize - totalMargins; + + const numberOfTicks = Math.floor( + availableAxisSize / + (layout === BarChartLayout.VERTICAL + ? BAR_CHART_MINIMUM_WIDTH_PER_TICK + : computeMinHeightPerTick({ axisFontSize })), + ); if (numberOfTicks <= 0) return []; if (numberOfTicks === 1) return [data[0][indexBy] as string | number]; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount.ts index f65368dbc0..f3484eda41 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount.ts @@ -1,14 +1,22 @@ -const MIN_TICK_SPACING_HEIGHT_RATIO = 2.5; +import { BAR_CHART_MINIMUM_WIDTH_PER_TICK } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; +import { computeMinHeightPerTick } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick'; type ComputeBarChartValueTickCountProps = { - height: number; + axisSize: number; axisFontSize: number; + layout: BarChartLayout; }; export const computeBarChartValueTickCount = ({ - height, + axisSize, axisFontSize, + layout, }: ComputeBarChartValueTickCountProps): number => { - const minHeightPerTick = axisFontSize * MIN_TICK_SPACING_HEIGHT_RATIO; - return Math.max(1, Math.floor(height / minHeightPerTick)); + const minTickSize = + layout === BarChartLayout.VERTICAL + ? computeMinHeightPerTick({ axisFontSize }) + : BAR_CHART_MINIMUM_WIDTH_PER_TICK; + + return Math.max(1, Math.floor(axisSize / minTickSize)); }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick.ts new file mode 100644 index 0000000000..cdcc62b4ea --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick.ts @@ -0,0 +1,9 @@ +import { BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinTickSpacingHeightRatio'; + +export const computeMinHeightPerTick = ({ + axisFontSize, +}: { + axisFontSize: number; +}): number => { + return axisFontSize * BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts index 86b3a37fa2..412a7b41b4 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartAxisConfigs.ts @@ -1,25 +1,29 @@ import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; -import { computeBarChartCategoryTickValues } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues'; -import { computeBarChartValueTickCount } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; +import { getBarChartTickConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig'; import { truncateTickLabel } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/truncateTickLabel'; import { formatGraphValue, type GraphValueFormatOptions, } from '@/page-layout/widgets/graph/utils/graphFormatters'; -const AVERAGE_CHARACTER_WIDTH_RATIO = 0.6; -const MIN_TICK_LABEL_LENGTH = 5; -const MAX_LEFT_AXIS_LABEL_LENGTH = 10; const LEFT_AXIS_LEGEND_OFFSET_PADDING = 5; const TICK_PADDING = 5; const BOTTOM_AXIS_LEGEND_OFFSET = 40; +const COMMON_AXIS_CONFIG = { + tickSize: 0, + tickPadding: TICK_PADDING, + tickRotation: 0, + legendPosition: 'middle' as const, +}; + type GetBarChartAxisConfigsProps = { width: number; height: number; data: BarChartDataItem[]; - layout: 'vertical' | 'horizontal'; + layout: BarChartLayout; indexBy: string; xAxisLabel?: string; yAxisLabel?: string; @@ -38,59 +42,43 @@ export const getBarChartAxisConfigs = ({ formatOptions, axisFontSize = 11, }: GetBarChartAxisConfigsProps) => { - const categoryTickValues = computeBarChartCategoryTickValues({ + const { + categoryTickValues, + numberOfValueTicks, + maxBottomAxisTickLabelLength, + maxLeftAxisTickLabelLength, + } = getBarChartTickConfig({ width, + height, data, indexBy, xAxisLabel, yAxisLabel, + axisFontSize, layout, }); const margins = getBarChartMargins({ xAxisLabel, yAxisLabel, layout }); - const availableWidth = width - (margins.left + margins.right); - const availableHeight = height - (margins.top + margins.bottom); - const widthPerTick = - categoryTickValues.length > 0 - ? availableWidth / categoryTickValues.length - : 0; - const averageCharacterWidth = axisFontSize * AVERAGE_CHARACTER_WIDTH_RATIO; - const maxLabelLength = Math.max( - MIN_TICK_LABEL_LENGTH, - Math.floor(widthPerTick / averageCharacterWidth), - ); - - const numberOfValueTicks = computeBarChartValueTickCount({ - height: availableHeight, - axisFontSize, - }); - - if (layout === 'vertical') { + if (layout === BarChartLayout.VERTICAL) { return { axisBottom: { - tickSize: 0, - tickPadding: TICK_PADDING, - tickRotation: 0, + ...COMMON_AXIS_CONFIG, tickValues: categoryTickValues, legend: xAxisLabel, - legendPosition: 'middle' as const, legendOffset: BOTTOM_AXIS_LEGEND_OFFSET, format: (value: string | number) => - truncateTickLabel(String(value), maxLabelLength), + truncateTickLabel(String(value), maxBottomAxisTickLabelLength), }, axisLeft: { - tickSize: 0, - tickPadding: TICK_PADDING, - tickRotation: 0, + ...COMMON_AXIS_CONFIG, tickValues: numberOfValueTicks, legend: yAxisLabel, - legendPosition: 'middle' as const, legendOffset: -margins.left + LEFT_AXIS_LEGEND_OFFSET_PADDING, format: (value: number) => truncateTickLabel( formatGraphValue(value, formatOptions ?? {}), - MAX_LEFT_AXIS_LABEL_LENGTH, + maxLeftAxisTickLabelLength, ), }, }; @@ -98,25 +86,23 @@ export const getBarChartAxisConfigs = ({ return { axisBottom: { - tickSize: 0, - tickPadding: TICK_PADDING, - tickRotation: 0, + ...COMMON_AXIS_CONFIG, tickValues: numberOfValueTicks, legend: yAxisLabel, - legendPosition: 'middle' as const, legendOffset: BOTTOM_AXIS_LEGEND_OFFSET, - format: (value: number) => formatGraphValue(value, formatOptions || {}), + format: (value: number) => + truncateTickLabel( + formatGraphValue(value, formatOptions ?? {}), + maxBottomAxisTickLabelLength, + ), }, axisLeft: { - tickSize: 0, - tickPadding: TICK_PADDING, - tickRotation: 0, + ...COMMON_AXIS_CONFIG, tickValues: categoryTickValues, legend: xAxisLabel, - legendPosition: 'middle' as const, legendOffset: -margins.left + LEFT_AXIS_LEGEND_OFFSET_PADDING, format: (value: string | number) => - truncateTickLabel(String(value), MAX_LEFT_AXIS_LABEL_LENGTH), + truncateTickLabel(String(value), maxLeftAxisTickLabelLength), }, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins.ts index 92719b8c0a..c281ac5276 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins.ts @@ -1,3 +1,4 @@ +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { isDefined } from 'twenty-shared/utils'; const BAR_CHART_MARGINS = { @@ -35,20 +36,20 @@ export const getBarChartMargins = ({ }: { xAxisLabel?: string; yAxisLabel?: string; - layout: 'vertical' | 'horizontal'; + layout: BarChartLayout; }) => { if (isDefined(xAxisLabel) && isDefined(yAxisLabel)) { return BAR_CHART_MARGINS_WITH_BOTH_LABELS; } if (isDefined(xAxisLabel)) { - return layout === 'horizontal' + return layout === BarChartLayout.HORIZONTAL ? BAR_CHART_MARGINS_WITH_Y_LABEL : BAR_CHART_MARGINS_WITH_X_LABEL; } if (isDefined(yAxisLabel)) { - return layout === 'horizontal' + return layout === BarChartLayout.HORIZONTAL ? BAR_CHART_MARGINS_WITH_X_LABEL : BAR_CHART_MARGINS_WITH_Y_LABEL; } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts new file mode 100644 index 0000000000..fcc1c3dc8a --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig.ts @@ -0,0 +1,80 @@ +import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; +import { calculateMaxTickLabelLength } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateMaxTickLabelLength'; +import { calculateWidthPerTick } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/calculateWidthPerTick'; +import { computeBarChartCategoryTickValues } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartCategoryTickValues'; +import { computeBarChartValueTickCount } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeBarChartValueTickCount'; +import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; + +const MAX_LEFT_AXIS_LABEL_LENGTH = 10; + +export type BarChartTickConfig = { + categoryTickValues: (string | number)[]; + numberOfValueTicks: number; + maxBottomAxisTickLabelLength: number; + maxLeftAxisTickLabelLength: number; +}; + +export const getBarChartTickConfig = ({ + width, + height, + data, + indexBy, + xAxisLabel, + yAxisLabel, + axisFontSize, + layout, +}: { + width: number; + height: number; + data: BarChartDataItem[]; + indexBy: string; + xAxisLabel?: string; + yAxisLabel?: string; + axisFontSize: number; + layout: BarChartLayout; +}): BarChartTickConfig => { + const categoryTickValues = computeBarChartCategoryTickValues({ + axisSize: layout === BarChartLayout.VERTICAL ? width : height, + axisFontSize, + data, + indexBy, + xAxisLabel, + yAxisLabel, + layout, + }); + + const margins = getBarChartMargins({ xAxisLabel, yAxisLabel, layout }); + + const availableWidth = width - (margins.left + margins.right); + const availableHeight = height - (margins.top + margins.bottom); + + const numberOfValueTicks = computeBarChartValueTickCount({ + axisSize: + layout === BarChartLayout.VERTICAL ? availableHeight : availableWidth, + axisFontSize, + layout, + }); + + const widthPerTick = calculateWidthPerTick({ + layout, + availableWidth, + categoryTickCount: categoryTickValues.length, + valueTickCount: numberOfValueTicks, + }); + + const maxBottomAxisTickLabelLength = calculateMaxTickLabelLength({ + widthPerTick, + axisFontSize, + }); + + // TODO: Make this dynamic based on the data + const maxLeftAxisTickLabelLength = MAX_LEFT_AXIS_LABEL_LENGTH; + + return { + categoryTickValues, + numberOfValueTicks, + maxBottomAxisTickLabelLength, + maxLeftAxisTickLabelLength, + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToBarChartData.ts index f125e6c9c8..9436b8f0d2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToBarChartData.ts @@ -1,9 +1,10 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; +import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { getGroupByQueryName } from '@/page-layout/utils/getGroupByQueryName'; import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult'; import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults'; @@ -32,7 +33,7 @@ type TransformGroupByDataToBarChartDataResult = { xAxisLabel?: string; yAxisLabel?: string; showDataLabels: boolean; - layout?: 'vertical' | 'horizontal'; + layout?: BarChartLayout; hasTooManyGroups: boolean; }; @@ -44,7 +45,7 @@ const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = { xAxisLabel: undefined, yAxisLabel: undefined, showDataLabels: false, - layout: 'vertical', + layout: BarChartLayout.VERTICAL, hasTooManyGroups: false, }; @@ -82,8 +83,8 @@ export const transformGroupByDataToBarChartData = ({ ...EMPTY_BAR_CHART_RESULT, layout: configuration.graphType === GraphType.HORIZONTAL_BAR - ? 'horizontal' - : 'vertical', + ? BarChartLayout.HORIZONTAL + : BarChartLayout.VERTICAL, }; } @@ -104,8 +105,8 @@ export const transformGroupByDataToBarChartData = ({ indexBy: indexByKey, layout: configuration.graphType === GraphType.HORIZONTAL_BAR - ? 'horizontal' - : 'vertical', + ? BarChartLayout.HORIZONTAL + : BarChartLayout.VERTICAL, }; } @@ -162,8 +163,8 @@ export const transformGroupByDataToBarChartData = ({ const layout = configuration.graphType === GraphType.HORIZONTAL_BAR - ? 'horizontal' - : 'vertical'; + ? BarChartLayout.HORIZONTAL + : BarChartLayout.VERTICAL; return { ...baseResult,