Fix horizontal bar chart spacing between ticks (#15702)

This PR fixes a bug where the spacing between ticks on the Y axis was
adjusted according to the width of the widget where it should have been
adjusted according to the height. Same for the X axis.

**Before**:
Works for the vertical layout but not for the horizontal layout


https://github.com/user-attachments/assets/f0b94103-1ba6-4dbe-bbc6-c47541c1b5fa


**After**:
Works for both


https://github.com/user-attachments/assets/165faada-2943-4e05-92d6-a31def569500
This commit is contained in:
Raphaël Bosi
2025-11-12 11:19:32 +01:00
committed by GitHub
parent 065923d724
commit ea56586766
19 changed files with 252 additions and 95 deletions
@@ -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<typeof GraphWidgetBarChart> = {
@@ -280,7 +281,7 @@ export const Horizontal: Story = {
],
indexBy: 'product',
keys: ['score'],
layout: 'horizontal',
layout: BarChartLayout.HORIZONTAL,
showLegend: false,
showGrid: true,
xAxisLabel: 'Score',
@@ -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<D extends BarDatum> = BarItemProps<D> & {
groupMode?: 'grouped' | 'stacked';
data?: readonly D[];
indexBy?: string;
layout?: 'vertical' | 'horizontal';
layout?: BarChartLayout;
chartId?: string;
};
@@ -46,7 +47,7 @@ export const CustomBarItem = <D extends BarDatum>({
groupMode = 'grouped',
data: chartData,
indexBy,
layout = 'vertical',
layout = BarChartLayout.VERTICAL,
chartId,
}: CustomBarItemProps<D>) => {
const handleClick = useCallback(
@@ -123,7 +124,7 @@ export const CustomBarItem = <D extends BarDatum>({
barData.indexValue,
]);
const isHorizontal = layout === 'horizontal';
const isHorizontal = layout === BarChartLayout.HORIZONTAL;
const clipPathId = `round-corner-${chartId ?? 'chart'}-${barData.index}-${
seriesIndex >= 0 ? seriesIndex : 'x'
}`;
@@ -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'
@@ -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<string, string>;
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={
@@ -0,0 +1 @@
export const BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO = 2.5;
@@ -0,0 +1 @@
export const BAR_CHART_MINIMUM_WIDTH_PER_TICK = 100;
@@ -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;
@@ -0,0 +1,4 @@
export enum BarChartLayout {
VERTICAL = 'vertical',
HORIZONTAL = 'horizontal',
}
@@ -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,
@@ -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<BarChartDataItem>,
layout: 'vertical' | 'horizontal',
layout: BarChartLayout,
) => {
if (layout === 'vertical') {
if (layout === BarChartLayout.VERTICAL) {
return {
x1: bar.x,
x2: bar.x + bar.width,
@@ -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);
};
@@ -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;
};
@@ -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];
@@ -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));
};
@@ -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;
};
@@ -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),
},
};
};
@@ -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;
}
@@ -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,
};
};
@@ -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,