Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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> = {
Expand Down Expand Up @@ -276,7 +277,7 @@ export const Horizontal: Story = {
],
indexBy: 'product',
keys: ['score'],
layout: 'horizontal',
layout: BarChartLayout.HORIZONTAL,
showLegend: false,
showGrid: true,
xAxisLabel: 'Score',
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { Text } from '@nivo/text';
import { useTheme } from '@nivo/theming';
Expand All @@ -15,7 +16,7 @@ type CustomBarItemProps<D extends BarDatum> = BarItemProps<D> & {
groupMode?: 'grouped' | 'stacked';
data?: readonly D[];
indexBy?: string;
layout?: 'vertical' | 'horizontal';
layout?: BarChartLayout;
chartId?: string;
};

Expand Down Expand Up @@ -63,7 +64,7 @@ export const CustomBarItem = <D extends BarDatum>({
groupMode = 'grouped',
data: chartData,
indexBy,
layout = 'vertical',
layout = BarChartLayout.VERTICAL,
chartId,
}: CustomBarItemProps<D>) => {
const theme = useTheme();
Expand Down Expand Up @@ -158,7 +159,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'
}`;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,7 +11,7 @@ type CustomTotalsLayerProps = Pick<
> & {
formatValue?: (value: number) => string;
offset?: number;
layout?: 'vertical' | 'horizontal';
layout?: BarChartLayout;
groupMode?: 'grouped' | 'stacked';
omitNullValues?: boolean;
};
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useBarChartHandlers } from '@/page-layout/widgets/graph/graphWidgetBarC
import { useBarChartTheme } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartTheme';
import { useBarChartTooltip } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useBarChartTooltip';
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';
Expand Down Expand Up @@ -40,7 +41,7 @@ type GraphWidgetBarChartProps = {
xAxisLabel?: string;
yAxisLabel?: string;
id: string;
layout?: 'vertical' | 'horizontal';
layout?: BarChartLayout;
groupMode?: 'grouped' | 'stacked';
seriesLabels?: Record<string, string>;
rangeMin?: number;
Expand Down Expand Up @@ -69,7 +70,7 @@ export const GraphWidgetBarChart = ({
xAxisLabel,
yAxisLabel,
id,
layout = 'vertical',
layout = BarChartLayout.VERTICAL,
groupMode,
seriesLabels,
rangeMin,
Expand Down Expand Up @@ -201,7 +202,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,
Expand Down Expand Up @@ -250,10 +251,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={
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO = 2.5;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BAR_CHART_MINIMUM_WIDTH_PER_TICK = 100;
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -19,7 +20,7 @@ type UseGraphBarChartWidgetDataResult = {
xAxisLabel?: string;
yAxisLabel?: string;
showDataLabels: boolean;
layout?: 'vertical' | 'horizontal';
layout?: BarChartLayout;
loading: boolean;
error?: Error;
hasTooManyGroups: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum BarChartLayout {
VERTICAL = 'vertical',
HORIZONTAL = 'horizontal',
}
Original file line number Diff line number Diff line change
@@ -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 = (
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
};
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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];
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
};
Original file line number Diff line number Diff line change
@@ -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;
};
Loading