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:
+2
-1
@@ -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',
|
||||
|
||||
+4
-3
@@ -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'
|
||||
}`;
|
||||
|
||||
+4
-3
@@ -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'
|
||||
|
||||
+8
-7
@@ -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={
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO = 2.5;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const BAR_CHART_MINIMUM_WIDTH_PER_TICK = 100;
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export enum BarChartLayout {
|
||||
VERTICAL = 'vertical',
|
||||
HORIZONTAL = 'horizontal',
|
||||
}
|
||||
+20
-7
@@ -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,
|
||||
|
||||
+3
-2
@@ -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,
|
||||
|
||||
+15
@@ -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);
|
||||
};
|
||||
+19
@@ -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;
|
||||
};
|
||||
+22
-9
@@ -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];
|
||||
|
||||
+13
-5
@@ -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));
|
||||
};
|
||||
|
||||
+9
@@ -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;
|
||||
};
|
||||
+31
-45
@@ -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),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
+4
-3
@@ -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;
|
||||
}
|
||||
|
||||
+80
@@ -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,
|
||||
};
|
||||
};
|
||||
+10
-9
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user