From 576019e465fa259e8bf5dc4637e72986c7a2068d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:39:42 +0100 Subject: [PATCH] [DASHBOARDS] Rotate ticks on bar and line charts (#16528) ## Description 3 steps depending on the widget width. From bigger to tighter space: - Fully shown horizontal text - Rotated text - Rotated text with skipped ticks to avoid overlapping Also created common files for all constants for bar and pie charts. Since a lot of them are shared, they can be inherited from a common file. ## Video QA https://github.com/user-attachments/assets/fd58d412-1a8b-4bd6-a420-4c03767e98d5 --- ...rtAggregateOperationSelectableListItem.tsx | 4 +- ...egateOperationSelectionDropdownContent.tsx | 2 +- ...ioAggregateOperationSelectableListItem.tsx | 4 +- .../page-layout/utils/getChartLimitMessage.ts | 8 +- .../graph/components/GraphWidgetLegend.tsx | 6 +- .../graph/constants/CommonChartConstants.ts | 24 +++++ ...ts => DashboardAggregateOperationRatio.ts} | 0 ...t.ts => ExtraItemToDetectTooManyGroups.ts} | 0 ...stant.ts => GraphDefaultAggregateValue.ts} | 0 ...tant.ts => GraphDefaultDateGranularity.ts} | 0 ...onstant.ts => LegendItemEstimatedWidth.ts} | 0 ...dth.constant.ts => LegendLabelMaxWidth.ts} | 0 ...nt.ts => LegendPaginationControlsWidth.ts} | 0 .../graph/constants/RotationThresholdWidth.ts | 1 + .../types/AggregateChartOperation.ts | 2 +- ...gregateOperationsForAggregateChart.test.ts | 2 +- .../utils/getAggregateChartOperationLabel.ts | 2 +- ...bleAggregateOperationsForAggregateChart.ts | 2 +- .../components/CustomBarItem.tsx | 13 +-- .../components/GraphBarChartTooltip.tsx | 4 +- .../components/GraphWidgetBarChart.tsx | 33 +++---- .../constants/BarChartConstants.ts | 25 +++++ ...GranularitiesWithoutGapFilling.constant.ts | 8 -- .../constants/BarChartDefaultInnerPadding.ts | 1 - .../constants/BarChartHoverBrightness.ts | 1 - .../BarChartMaximumNumberOfBars.constant.ts | 1 - ...artMaximumNumberOfGroupsPerBar.constant.ts | 1 - .../BarChartMaximumValueTickCount.ts | 1 - .../BarChartMinTickSpacingHeightRatio.ts | 1 - .../BarChartMinimumValueTickCount.ts | 1 - .../constants/BarChartMinimumWidthPerTick.ts | 1 - .../constants/BarChartOuterPaddingRatio.ts | 1 - .../constants/BarChartTooltipOffsetPx.ts | 1 - .../BarChartTooltipScrollableItemThreshold.ts | 1 - .../constants/MaximumBarWidth.ts | 1 - .../generateDateGroupsInRange.test.ts | 8 +- .../__tests__/getBarChartQueryLimit.test.ts | 15 +-- .../utils/__tests__/truncateTickLabel.test.ts | 40 ++++++++ .../utils/calculateMaxTickLabelLength.ts | 12 ++- .../computeBarChartCategoryTickValues.ts | 18 ++-- .../utils/computeBarChartValueTickCount.ts | 4 +- .../utils/computeMinHeightPerTick.ts | 4 +- .../utils/fillDateGapsInBarChartData.ts | 8 +- .../utils/generateDateGroupsInRange.ts | 4 +- .../utils/getBarChartAxisConfigs.ts | 94 +++++++++---------- .../utils/getBarChartInnerPadding.ts | 23 +++-- .../utils/getBarChartMargins.ts | 33 +++---- .../utils/getBarChartQueryLimit.ts | 12 +-- .../utils/getBarChartTickConfig.ts | 34 ++++--- .../transformGroupByDataToBarChartData.ts | 2 +- ...formOneDimensionalGroupByToBarChartData.ts | 10 +- ...formTwoDimensionalGroupByToBarChartData.ts | 9 +- .../utils/truncateTickLabel.ts | 12 ++- .../components/CustomCrosshairLayer.tsx | 28 +++--- .../components/GraphLineChartTooltip.tsx | 4 +- .../components/GraphWidgetLineChart.tsx | 43 ++++----- .../GraphWidgetLineChartRenderer.tsx | 4 +- .../constants/LineAreaFillEndOpacity.ts | 1 - .../constants/LineAreaFillStartOpacity.ts | 1 - .../constants/LineChartConstants.ts | 18 ++++ .../constants/LineChartCrosshairDashArray.ts | 1 - .../LineChartCrosshairStrokeOpacity.ts | 1 - .../LineChartCrosshairStrokeWidth.ts | 1 - .../LineChartCrosshairTransitionDamping.ts | 1 - .../LineChartCrosshairTransitionStiffness.ts | 1 - .../constants/LineChartIsStackedDefault.ts | 1 - .../constants/LineChartMarginBottom.ts | 1 - .../constants/LineChartMarginLeft.ts | 1 - .../constants/LineChartMarginRight.ts | 1 - .../constants/LineChartMarginTop.ts | 1 - ...ChartMaximumNumberOfDataPoints.constant.ts | 1 - ...aximumNumberOfNonStackedSeries.constant.ts | 1 - ...rtMaximumNumberOfStackedSeries.constant.ts | 1 - .../constants/LineChartTooltipOffsetPx.ts | 1 - .../__tests__/getLineChartQueryLimit.test.ts | 16 ++-- .../computeLineChartCategoryTickValues.ts | 18 ++-- .../utils/createAreaFillDef.ts | 7 +- .../utils/getLineChartAxisBottomConfig.ts | 82 ++++++++++++++-- .../utils/getLineChartAxisLeftConfig.ts | 38 +++++--- .../utils/getLineChartQueryLimit.ts | 16 ++-- .../hooks/useGraphPieChartWidgetData.ts | 2 +- .../computeChartCategoryTickValues.test.ts | 57 +++++++++++ .../computeMaxLabelLengthForMargin.test.ts | 70 ++++++++++++++ .../graph/utils/buildGroupByFieldObject.ts | 2 +- .../computeAggregateNumericValueForGraph.ts | 2 +- .../utils/computeChartCategoryTickValues.ts | 13 +++ .../graph/utils/computeEffectiveValueRange.ts | 12 +-- .../utils/computeMaxLabelLengthForMargin.ts | 21 +++++ .../widgets/graph/utils/ellipsisLength.ts | 1 + .../graph/utils/formatDimensionValue.ts | 2 +- ...ariablesFromBarOrLineChartConfiguration.ts | 2 +- ...QueryVariablesFromPieChartConfiguration.ts | 2 +- .../widgets/graph/utils/getFieldOrderBy.ts | 2 +- .../graph/utils/getRelationFieldOrderBy.ts | 2 +- .../graph/utils/getTickRotationConfig.ts | 57 +++++++++++ ...ormOneDimensionalGroupByToLineChartData.ts | 6 +- ...ormTwoDimensionalGroupByToLineChartData.ts | 7 +- 97 files changed, 722 insertions(+), 322 deletions(-) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/constants/CommonChartConstants.ts rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{DashboardAggregateOperationRatio.constant.ts => DashboardAggregateOperationRatio.ts} (100%) rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{ExtraItemToDetectTooManyGroups.constant.ts => ExtraItemToDetectTooManyGroups.ts} (100%) rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{GraphDefaultAggregateValue.constant.ts => GraphDefaultAggregateValue.ts} (100%) rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{GraphDefaultDateGranularity.constant.ts => GraphDefaultDateGranularity.ts} (100%) rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{LegendItemEstimatedWidth.constant.ts => LegendItemEstimatedWidth.ts} (100%) rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{LegendLabelMaxWidth.constant.ts => LegendLabelMaxWidth.ts} (100%) rename packages/twenty-front/src/modules/page-layout/widgets/graph/constants/{LegendPaginationControlsWidth.constant.ts => LegendPaginationControlsWidth.ts} (100%) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/constants/RotationThresholdWidth.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartDateGranularitiesWithoutGapFilling.constant.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartDefaultInnerPadding.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartHoverBrightness.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfGroupsPerBar.constant.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumValueTickCount.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinTickSpacingHeightRatio.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumValueTickCount.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartOuterPaddingRatio.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartTooltipOffsetPx.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartTooltipScrollableItemThreshold.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/constants/MaximumBarWidth.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/truncateTickLabel.test.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineAreaFillEndOpacity.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineAreaFillStartOpacity.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairDashArray.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairStrokeOpacity.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairStrokeWidth.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairTransitionDamping.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairTransitionStiffness.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartIsStackedDefault.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginBottom.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginLeft.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginRight.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginTop.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfNonStackedSeries.constant.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfStackedSeries.constant.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartTooltipOffsetPx.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeMaxLabelLengthForMargin.test.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeMaxLabelLengthForMargin.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/ellipsisLength.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getTickRotationConfig.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx index 28599dcd0f..de6ecaad07 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectableListItem.tsx @@ -1,9 +1,9 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode'; -import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; -import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation'; +import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; import { type AggregateChartOperation } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx index aacf7113ab..38a34758d7 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx @@ -6,7 +6,7 @@ import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useW import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations'; import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType'; -import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; import { type AggregateChartOperation } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation'; import { getAggregateChartOperationLabel } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel'; import { getAvailableAggregateOperationsForAggregateChart } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAvailableAggregateOperationsForAggregateChart'; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx index 78a1add727..3654555cb9 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioAggregateOperationSelectableListItem.tsx @@ -1,6 +1,6 @@ -import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode'; import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord'; -import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode'; +import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext'; import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem'; import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState'; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartLimitMessage.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartLimitMessage.ts index aab4b88732..a8596ffc49 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartLimitMessage.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getChartLimitMessage.ts @@ -1,6 +1,6 @@ import { getDateGranularityPluralLabel } from '@/command-menu/pages/page-layout/utils/getDateGranularityPluralLabel'; -import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; -import { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; import { t } from '@lingui/core/macro'; import { type ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; @@ -24,10 +24,10 @@ export const getChartLimitMessage = ({ }: GetChartLimitMessageParams): string => { const maxItems = graphType === GraphType.LINE - ? LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS + ? LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS : graphType === GraphType.VERTICAL_BAR || graphType === GraphType.HORIZONTAL_BAR - ? BAR_CHART_MAXIMUM_NUMBER_OF_BARS + ? BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS : PIE_CHART_MAXIMUM_NUMBER_OF_SLICES; if (isPrimaryAxisDate && isDefined(primaryAxisDateGranularity)) { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx index 651a2cc595..5570816f2e 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx @@ -1,9 +1,9 @@ import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState'; import { GraphWidgetLegendDot } from '@/page-layout/widgets/graph/components/GraphWidgetLegendDot'; import { LEGEND_HIGHLIGHT_DIMMED_OPACITY } from '@/page-layout/widgets/graph/constants/LegendHighlightDimmedOpacity.constant'; -import { LEGEND_ITEM_ESTIMATED_WIDTH } from '@/page-layout/widgets/graph/constants/LegendItemEstimatedWidth.constant'; -import { LEGEND_LABEL_MAX_WIDTH } from '@/page-layout/widgets/graph/constants/LegendLabelMaxWidth.constant'; -import { LEGEND_PAGINATION_CONTROLS_WIDTH } from '@/page-layout/widgets/graph/constants/LegendPaginationControlsWidth.constant'; +import { LEGEND_ITEM_ESTIMATED_WIDTH } from '@/page-layout/widgets/graph/constants/LegendItemEstimatedWidth'; +import { LEGEND_LABEL_MAX_WIDTH } from '@/page-layout/widgets/graph/constants/LegendLabelMaxWidth'; +import { LEGEND_PAGINATION_CONTROLS_WIDTH } from '@/page-layout/widgets/graph/constants/LegendPaginationControlsWidth'; import { useLegendItemToggle } from '@/page-layout/widgets/graph/hooks/useLegendItemToggle'; import { graphWidgetHiddenLegendIdsComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHiddenLegendIdsComponentState'; import { graphWidgetHighlightedLegendIdComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHighlightedLegendIdComponentState'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/CommonChartConstants.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/CommonChartConstants.ts new file mode 100644 index 0000000000..495f62be8e --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/CommonChartConstants.ts @@ -0,0 +1,24 @@ +export const COMMON_CHART_CONSTANTS = { + MINIMUM_WIDTH_PER_TICK_ROTATED: 20, + ROTATED_LABELS_EXTRA_BOTTOM_MARGIN: 20, + MARGIN_TOP: 20, + MARGIN_RIGHT: 20, + MARGIN_LEFT_WITHOUT_LABEL: 65, + MARGIN_LEFT_WITH_LABEL: 80, + MARGIN_BOTTOM_WITHOUT_LABEL: 50, + MARGIN_BOTTOM_WITH_LABEL: 65, + TICK_PADDING: 5, + TICK_SIZE: 0, + AXIS_FONT_SIZE: 11, + LEFT_AXIS_LEGEND_OFFSET_PADDING: 5, + BOTTOM_AXIS_LEGEND_OFFSET: 55, + TICK_MINIMUM_NUMBER_OF_DISPLAYED_CHARACTERS: 10, + HORIZONTAL_LABEL_CHARACTER_WIDTH_RATIO: 0.6, + ROTATED_LABEL_CHARACTER_WIDTH_RATIO: 0.5, + TICK_ROTATION_ANGLE: -45, + TICK_PADDING_ALLOWANCE: 10, + MIN_TICK_LABEL_LENGTH: 5, + POSITIVE_RANGE_PADDING_RATIO: 0.1, + MINIMUM_POSITIVE_RANGE_PADDING: 1, + NO_ROTATION_ANGLE: 0, +} as const; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultAggregateValue.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultAggregateValue.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultAggregateValue.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultAggregateValue.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendItemEstimatedWidth.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendItemEstimatedWidth.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendItemEstimatedWidth.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendItemEstimatedWidth.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendLabelMaxWidth.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendLabelMaxWidth.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendLabelMaxWidth.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendLabelMaxWidth.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendPaginationControlsWidth.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendPaginationControlsWidth.ts similarity index 100% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendPaginationControlsWidth.constant.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/constants/LegendPaginationControlsWidth.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/RotationThresholdWidth.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/RotationThresholdWidth.ts new file mode 100644 index 0000000000..c82d448f89 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/constants/RotationThresholdWidth.ts @@ -0,0 +1 @@ +export const ROTATION_THRESHOLD_WIDTH = 50; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation.ts index 3ef8263595..8631c5159b 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation.ts @@ -1,5 +1,5 @@ import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; -import { type DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { type DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; export type AggregateChartOperation = | ExtendedAggregateOperations diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/__tests__/getAvailableAggregateOperationsForAggregateChart.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/__tests__/getAvailableAggregateOperationsForAggregateChart.test.ts index a5c79f2c8f..27d25c0cec 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/__tests__/getAvailableAggregateOperationsForAggregateChart.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/__tests__/getAvailableAggregateOperationsForAggregateChart.test.ts @@ -1,4 +1,4 @@ -import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { getAvailableAggregateOperationsForAggregateChart } from '../getAvailableAggregateOperationsForAggregateChart'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts index 5b31617612..4d1e26eb4b 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts @@ -1,5 +1,5 @@ import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; -import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; import { type AggregateChartOperation } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation'; import { t } from '@lingui/core/macro'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAvailableAggregateOperationsForAggregateChart.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAvailableAggregateOperationsForAggregateChart.ts index e4227245c4..fd687da3b4 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAvailableAggregateOperationsForAggregateChart.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAvailableAggregateOperationsForAggregateChart.ts @@ -1,5 +1,5 @@ import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType'; -import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio.constant'; +import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio'; import { type AggregateChartOperation } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/types/AggregateChartOperation'; import { FieldMetadataType } from '~/generated-metadata/graphql'; 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 a4f1bfb03d..8a066fa499 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,6 +1,5 @@ import { LEGEND_HIGHLIGHT_DIMMED_OPACITY } from '@/page-layout/widgets/graph/constants/LegendHighlightDimmedOpacity.constant'; -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 { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { graphWidgetHighlightedLegendIdComponentState } from '@/page-layout/widgets/graph/states/graphWidgetHighlightedLegendIdComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -33,7 +32,9 @@ const StyledBarRect = styled(animated.rect)<{ &:hover { filter: ${({ $isInteractive }) => - $isInteractive ? `brightness(${BAR_CHART_HOVER_BRIGHTNESS})` : 'none'}; + $isInteractive + ? `brightness(${BAR_CHART_CONSTANTS.HOVER_BRIGHTNESS})` + : 'none'}; } `; @@ -152,12 +153,12 @@ export const CustomBarItem = ({ const constrainedThicknessDimension = to( unconstrainedThicknessDimension, - (dimension) => Math.min(dimension, BAR_CHART_MAXIMUM_WIDTH), + (dimension) => Math.min(dimension, BAR_CHART_CONSTANTS.MAXIMUM_WIDTH), ); const centeringOffset = to(unconstrainedThicknessDimension, (dimension) => - dimension > BAR_CHART_MAXIMUM_WIDTH - ? (dimension - BAR_CHART_MAXIMUM_WIDTH) / 2 + dimension > BAR_CHART_CONSTANTS.MAXIMUM_WIDTH + ? (dimension - BAR_CHART_CONSTANTS.MAXIMUM_WIDTH) / 2 : 0, ); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx index 2cb006f88d..b0d9346631 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphBarChartTooltip.tsx @@ -1,5 +1,5 @@ import { GraphWidgetFloatingTooltip } from '@/page-layout/widgets/graph/components/GraphWidgetFloatingTooltip'; -import { BAR_CHART_TOOLTIP_OFFSET_PX } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartTooltipOffsetPx'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { graphWidgetBarTooltipComponentState } from '@/page-layout/widgets/graph/graphWidgetBarChart/states/graphWidgetBarTooltipComponentState'; import { type BarChartEnrichedKey } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartEnrichedKey'; import { getBarChartTooltipData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTooltipData'; @@ -63,7 +63,7 @@ export const GraphBarChartTooltip = ({ (props: BarItemProps) => ( @@ -260,8 +257,6 @@ export const GraphWidgetBarChart = ({ ] : undefined; - const margins = getBarChartMargins({ xAxisLabel, yAxisLabel, layout }); - return ( { }); expect(result.dates.length).toBeLessThanOrEqual( - BAR_CHART_MAXIMUM_NUMBER_OF_BARS, + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS, + ); + expect(result.dates.length).toBe( + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS, ); - expect(result.dates.length).toBe(BAR_CHART_MAXIMUM_NUMBER_OF_BARS); expect(result.wasTruncated).toBe(true); }); }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/getBarChartQueryLimit.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/getBarChartQueryLimit.test.ts index 8b3a299921..01f6b52f01 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/getBarChartQueryLimit.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/getBarChartQueryLimit.test.ts @@ -1,6 +1,5 @@ -import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant'; -import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; -import { BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfGroupsPerBar.constant'; +import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { type BarChartConfiguration, BarChartGroupMode, @@ -16,7 +15,8 @@ describe('getBarChartQueryLimit', () => { } as BarChartConfiguration); expect(result).toBe( - BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS + + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, ); }); @@ -28,8 +28,8 @@ describe('getBarChartQueryLimit', () => { } as BarChartConfiguration); expect(result).toBe( - BAR_CHART_MAXIMUM_NUMBER_OF_BARS * - BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR + + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS * + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_GROUPS_PER_BAR + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, ); }); @@ -42,7 +42,8 @@ describe('getBarChartQueryLimit', () => { } as BarChartConfiguration); expect(result).toBe( - BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS + + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, ); }); }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/truncateTickLabel.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/truncateTickLabel.test.ts new file mode 100644 index 0000000000..50ba6d8014 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/truncateTickLabel.test.ts @@ -0,0 +1,40 @@ +import { truncateTickLabel } from '../truncateTickLabel'; + +describe('truncateTickLabel', () => { + it('should return the original string when it fits within maxLength', () => { + expect(truncateTickLabel('Hello', 10)).toBe('Hello'); + expect(truncateTickLabel('Test', 4)).toBe('Test'); + }); + + it('should truncate and add ellipsis when string exceeds maxLength', () => { + expect(truncateTickLabel('Hello World', 8)).toBe('Hello...'); + expect(truncateTickLabel('LongLabel', 6)).toBe('Lon...'); + }); + + it('should handle numeric values', () => { + expect(truncateTickLabel(12345, 4)).toBe('1...'); + expect(truncateTickLabel(12345, 10)).toBe('12345'); + }); + + it('should return partial ellipsis when maxLength is 3 or less', () => { + expect(truncateTickLabel('Hello', 3)).toBe('...'); + expect(truncateTickLabel('Hello', 2)).toBe('..'); + expect(truncateTickLabel('Hello', 1)).toBe('.'); + }); + + it('should handle maxLength of 0', () => { + expect(truncateTickLabel('Hello', 0)).toBe(''); + }); + + it('should handle empty string', () => { + expect(truncateTickLabel('', 5)).toBe(''); + }); + + it('should handle maxLength equal to string length', () => { + expect(truncateTickLabel('Hello', 5)).toBe('Hello'); + }); + + it('should handle maxLength of 4 correctly', () => { + expect(truncateTickLabel('Hello', 4)).toBe('H...'); + }); +}); 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 index 31fff8421b..af3fa252a0 100644 --- 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 @@ -1,5 +1,4 @@ -const AVERAGE_CHARACTER_WIDTH_RATIO = 0.6; -const MIN_TICK_LABEL_LENGTH = 5; +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; export const calculateMaxTickLabelLength = ({ widthPerTick, @@ -8,8 +7,13 @@ export const calculateMaxTickLabelLength = ({ widthPerTick: number; axisFontSize: number; }): number => { - const averageCharacterWidth = axisFontSize * AVERAGE_CHARACTER_WIDTH_RATIO; + const averageCharacterWidth = + axisFontSize * + COMMON_CHART_CONSTANTS.HORIZONTAL_LABEL_CHARACTER_WIDTH_RATIO; const calculatedLength = Math.floor(widthPerTick / averageCharacterWidth); - return Math.max(MIN_TICK_LABEL_LENGTH, calculatedLength); + return Math.max( + COMMON_CHART_CONSTANTS.MIN_TICK_LABEL_LENGTH, + calculatedLength, + ); }; 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 8b5b8f2ec2..acba82f045 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,4 +1,4 @@ -import { BAR_CHART_MINIMUM_WIDTH_PER_TICK } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; 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'; @@ -37,14 +37,20 @@ export const computeBarChartCategoryTickValues = ({ const availableAxisSize = axisSize - totalMargins; - const minimumSizePerTick = - layout === BarChartLayout.VERTICAL - ? BAR_CHART_MINIMUM_WIDTH_PER_TICK - : computeMinHeightPerTick({ axisFontSize }); + if (layout === BarChartLayout.HORIZONTAL) { + return computeChartCategoryTickValues({ + availableSize: availableAxisSize, + minimumSizePerTick: computeMinHeightPerTick({ axisFontSize }), + values, + }); + } + + const widthPerTick = data.length > 0 ? availableAxisSize / data.length : 0; return computeChartCategoryTickValues({ availableSize: availableAxisSize, - minimumSizePerTick, + minimumSizePerTick: BAR_CHART_CONSTANTS.MINIMUM_WIDTH_PER_TICK_ROTATED, values, + widthPerTick, }); }; 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 f3484eda41..5d815a867d 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,4 +1,4 @@ -import { BAR_CHART_MINIMUM_WIDTH_PER_TICK } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumWidthPerTick'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { computeMinHeightPerTick } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/computeMinHeightPerTick'; @@ -16,7 +16,7 @@ export const computeBarChartValueTickCount = ({ const minTickSize = layout === BarChartLayout.VERTICAL ? computeMinHeightPerTick({ axisFontSize }) - : BAR_CHART_MINIMUM_WIDTH_PER_TICK; + : BAR_CHART_CONSTANTS.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 index cdcc62b4ea..26f05a5eda 100644 --- 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 @@ -1,9 +1,9 @@ -import { BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinTickSpacingHeightRatio'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; export const computeMinHeightPerTick = ({ axisFontSize, }: { axisFontSize: number; }): number => { - return axisFontSize * BAR_CHART_MIN_TICK_SPACING_HEIGHT_RATIO; + return axisFontSize * BAR_CHART_CONSTANTS.MIN_TICK_SPACING_HEIGHT_RATIO; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData.ts index d77489b133..94ca547b9e 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData.ts @@ -1,4 +1,4 @@ -import { BAR_CHART_DATE_GRANULARITIES_WITHOUT_GAP_FILLING } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartDateGranularitiesWithoutGapFilling.constant'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { fillDateGapsInOneDimensionalBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInOneDimensionalBarChartData'; import { fillDateGapsInTwoDimensionalBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInTwoDimensionalBarChartData'; import { type SupportedDateGranularity } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getDateGroupsFromData'; @@ -25,7 +25,11 @@ export const fillDateGapsInBarChartData = ({ return { data, wasTruncated: false }; } - if (BAR_CHART_DATE_GRANULARITIES_WITHOUT_GAP_FILLING.has(dateGranularity)) { + if ( + BAR_CHART_CONSTANTS.DATE_GRANULARITIES_WITHOUT_GAP_FILLING.has( + dateGranularity, + ) + ) { return { data, wasTruncated: false }; } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/generateDateGroupsInRange.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/generateDateGroupsInRange.ts index f8fd2ab64a..194fbae642 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/generateDateGroupsInRange.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/generateDateGroupsInRange.ts @@ -1,4 +1,4 @@ -import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; @@ -31,7 +31,7 @@ export const generateDateGroupsInRange = ({ let currentDateCursor = new Date(startDate); while (currentDateCursor <= endDate) { - if (iterations >= BAR_CHART_MAXIMUM_NUMBER_OF_BARS) { + if (iterations >= BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS) { wasTruncated = true; break; } 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 409821dfc4..fbc24731a8 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,51 +1,29 @@ import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { getBarChartMargins } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartMargins'; -import { - type BarChartTickConfig, - 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'; -import { type BarDatum } from '@nivo/bar'; -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, -}; +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; +import { type BarChartTickConfig } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartTickConfig'; type GetBarChartAxisConfigsProps = { - width: number; - height: number; - data: BarDatum[]; layout: BarChartLayout; - indexBy: string; xAxisLabel?: string; yAxisLabel?: string; formatOptions?: GraphValueFormatOptions; - axisFontSize?: number; valueTickValues?: number[]; - tickConfig?: BarChartTickConfig; + tickConfig: BarChartTickConfig; }; export const getBarChartAxisConfigs = ({ - width, - height, - data, layout, - indexBy, xAxisLabel, yAxisLabel, formatOptions, - axisFontSize = 11, valueTickValues, tickConfig, }: GetBarChartAxisConfigsProps) => { @@ -54,55 +32,72 @@ export const getBarChartAxisConfigs = ({ numberOfValueTicks, maxBottomAxisTickLabelLength, maxLeftAxisTickLabelLength, - } = - tickConfig ?? - getBarChartTickConfig({ - width, - height, - data, - indexBy, - xAxisLabel, - yAxisLabel, - axisFontSize, - layout, - }); + bottomAxisTickRotation, + } = tickConfig; - const margins = getBarChartMargins({ xAxisLabel, yAxisLabel, layout }); const resolvedValueTickValues = valueTickValues && valueTickValues.length > 0 ? valueTickValues : numberOfValueTicks; + const baseMargins = getBarChartMargins({ xAxisLabel, yAxisLabel, layout }); + + const hasRotation = bottomAxisTickRotation !== 0; + const margins = + layout === BarChartLayout.VERTICAL && hasRotation + ? { + ...baseMargins, + bottom: + baseMargins.bottom + + COMMON_CHART_CONSTANTS.ROTATED_LABELS_EXTRA_BOTTOM_MARGIN, + } + : baseMargins; + if (layout === BarChartLayout.VERTICAL) { return { axisBottom: { - ...COMMON_AXIS_CONFIG, + tickSize: BAR_CHART_CONSTANTS.TICK_SIZE, + tickPadding: BAR_CHART_CONSTANTS.TICK_PADDING, + legendPosition: 'middle' as const, tickValues: categoryTickValues, + tickRotation: bottomAxisTickRotation, legend: xAxisLabel, - legendOffset: BOTTOM_AXIS_LEGEND_OFFSET, + legendOffset: + BAR_CHART_CONSTANTS.BOTTOM_AXIS_LEGEND_OFFSET + + (hasRotation + ? BAR_CHART_CONSTANTS.ROTATED_LABELS_EXTRA_BOTTOM_MARGIN + : 0), format: (value: string | number) => truncateTickLabel(String(value), maxBottomAxisTickLabelLength), }, axisLeft: { - ...COMMON_AXIS_CONFIG, + tickSize: BAR_CHART_CONSTANTS.TICK_SIZE, + tickPadding: BAR_CHART_CONSTANTS.TICK_PADDING, + legendPosition: 'middle' as const, + tickRotation: COMMON_CHART_CONSTANTS.NO_ROTATION_ANGLE, tickValues: resolvedValueTickValues, legend: yAxisLabel, - legendOffset: -margins.left + LEFT_AXIS_LEGEND_OFFSET_PADDING, + legendOffset: + -margins.left + BAR_CHART_CONSTANTS.LEFT_AXIS_LEGEND_OFFSET_PADDING, format: (value: number) => truncateTickLabel( formatGraphValue(value, formatOptions ?? {}), maxLeftAxisTickLabelLength, ), }, + margins, }; } return { axisBottom: { - ...COMMON_AXIS_CONFIG, + tickSize: BAR_CHART_CONSTANTS.TICK_SIZE, + tickPadding: BAR_CHART_CONSTANTS.TICK_PADDING, + legendPosition: 'middle' as const, + tickRotation: BAR_CHART_CONSTANTS.NO_ROTATION_ANGLE, tickValues: resolvedValueTickValues, legend: yAxisLabel, - legendOffset: BOTTOM_AXIS_LEGEND_OFFSET, + legendOffset: BAR_CHART_CONSTANTS.BOTTOM_AXIS_LEGEND_OFFSET, format: (value: number) => truncateTickLabel( formatGraphValue(value, formatOptions ?? {}), @@ -110,12 +105,17 @@ export const getBarChartAxisConfigs = ({ ), }, axisLeft: { - ...COMMON_AXIS_CONFIG, + tickSize: COMMON_CHART_CONSTANTS.TICK_SIZE, + tickPadding: COMMON_CHART_CONSTANTS.TICK_PADDING, + legendPosition: 'middle' as const, + tickRotation: COMMON_CHART_CONSTANTS.NO_ROTATION_ANGLE, tickValues: categoryTickValues, legend: xAxisLabel, - legendOffset: -margins.left + LEFT_AXIS_LEGEND_OFFSET_PADDING, + legendOffset: + -margins.left + BAR_CHART_CONSTANTS.LEFT_AXIS_LEGEND_OFFSET_PADDING, format: (value: string | number) => truncateTickLabel(String(value), maxLeftAxisTickLabelLength), }, + margins, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartInnerPadding.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartInnerPadding.ts index ce40caeec2..2c1e31f0e5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartInnerPadding.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartInnerPadding.ts @@ -1,5 +1,4 @@ -import { BAR_CHART_DEFAULT_INNER_PADDING } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartDefaultInnerPadding'; -import { BAR_CHART_OUTER_PADDING_RATIO } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartOuterPaddingRatio'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; type BarChartMargins = { @@ -19,8 +18,6 @@ type GetBarChartInnerPaddingProps = { groupMode?: 'grouped' | 'stacked'; }; -const MINIMUM_BAR_WIDTH = 2; - export const getBarChartInnerPadding = ({ chartWidth, chartHeight, @@ -35,7 +32,7 @@ export const getBarChartInnerPadding = ({ } if (dataLength === 0 || keysLength === 0) { - return BAR_CHART_DEFAULT_INNER_PADDING; + return BAR_CHART_CONSTANTS.DEFAULT_INNER_PADDING; } const availableSpace = @@ -44,13 +41,21 @@ export const getBarChartInnerPadding = ({ : chartHeight - margins.top - margins.bottom; const spacePerGroup = - (availableSpace / dataLength) * (1 - BAR_CHART_OUTER_PADDING_RATIO); + (availableSpace / dataLength) * + (1 - BAR_CHART_CONSTANTS.OUTER_PADDING_RATIO); const spacePerBar = spacePerGroup / keysLength; - if (spacePerBar < MINIMUM_BAR_WIDTH + BAR_CHART_DEFAULT_INNER_PADDING) { - return Math.max(0, (spacePerBar - MINIMUM_BAR_WIDTH) / 2); + if ( + spacePerBar < + BAR_CHART_CONSTANTS.MINIMUM_BAR_WIDTH + + BAR_CHART_CONSTANTS.DEFAULT_INNER_PADDING + ) { + return Math.max( + 0, + (spacePerBar - BAR_CHART_CONSTANTS.MINIMUM_BAR_WIDTH) / 2, + ); } - return BAR_CHART_DEFAULT_INNER_PADDING; + return BAR_CHART_CONSTANTS.DEFAULT_INNER_PADDING; }; 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 c281ac5276..beac97f818 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,32 +1,33 @@ +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { isDefined } from 'twenty-shared/utils'; const BAR_CHART_MARGINS = { - top: 20, - right: 20, - bottom: 45, - left: 65, + top: COMMON_CHART_CONSTANTS.MARGIN_TOP, + right: COMMON_CHART_CONSTANTS.MARGIN_RIGHT, + bottom: COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITHOUT_LABEL, + left: COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITHOUT_LABEL, } as const; const BAR_CHART_MARGINS_WITH_BOTH_LABELS = { - top: 20, - right: 20, - bottom: 60, - left: 80, + top: COMMON_CHART_CONSTANTS.MARGIN_TOP, + right: COMMON_CHART_CONSTANTS.MARGIN_RIGHT, + bottom: COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITH_LABEL, + left: COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITH_LABEL, } as const; const BAR_CHART_MARGINS_WITH_X_LABEL = { - top: 20, - right: 20, - bottom: 60, - left: 65, + top: COMMON_CHART_CONSTANTS.MARGIN_TOP, + right: COMMON_CHART_CONSTANTS.MARGIN_RIGHT, + bottom: COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITH_LABEL, + left: COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITHOUT_LABEL, } as const; const BAR_CHART_MARGINS_WITH_Y_LABEL = { - top: 20, - right: 20, - bottom: 45, - left: 80, + top: COMMON_CHART_CONSTANTS.MARGIN_TOP, + right: COMMON_CHART_CONSTANTS.MARGIN_RIGHT, + bottom: COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITHOUT_LABEL, + left: COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITH_LABEL, } as const; export const getBarChartMargins = ({ diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit.ts index e79c9626b2..0c89c561a1 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit.ts @@ -1,6 +1,5 @@ -import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant'; -import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; -import { BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfGroupsPerBar.constant'; +import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { isChartConfigurationTwoDimensional } from '@/page-layout/widgets/graph/utils/isChartConfigurationTwoDimensional'; import { BarChartGroupMode, @@ -15,13 +14,14 @@ export const getBarChartQueryLimit = ( configuration.groupMode === BarChartGroupMode.STACKED ) { return ( - BAR_CHART_MAXIMUM_NUMBER_OF_BARS * - BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR + + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS * + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_GROUPS_PER_BAR + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS ); } return ( - BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS + + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS ); }; 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 index 331764393a..ab9c1650be 100644 --- 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 @@ -1,20 +1,19 @@ -import { BAR_CHART_MAXIMUM_VALUE_TICK_COUNT } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumValueTickCount'; -import { BAR_CHART_MINIMUM_VALUE_TICK_COUNT } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMinimumValueTickCount'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; 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'; +import { computeMaxLabelLengthForMargin } from '@/page-layout/widgets/graph/utils/computeMaxLabelLengthForMargin'; +import { getTickRotationConfig } from '@/page-layout/widgets/graph/utils/getTickRotationConfig'; import { type BarDatum } from '@nivo/bar'; -const MAX_LEFT_AXIS_LABEL_LENGTH = 10; - export type BarChartTickConfig = { categoryTickValues: (string | number)[]; numberOfValueTicks: number; maxBottomAxisTickLabelLength: number; maxLeftAxisTickLabelLength: number; + bottomAxisTickRotation: number; }; export const getBarChartTickConfig = ({ @@ -38,8 +37,8 @@ export const getBarChartTickConfig = ({ }): BarChartTickConfig => { const clampValueTickCount = (tickCount: number) => Math.min( - BAR_CHART_MAXIMUM_VALUE_TICK_COUNT, - Math.max(BAR_CHART_MINIMUM_VALUE_TICK_COUNT, tickCount), + BAR_CHART_CONSTANTS.MAXIMUM_VALUE_TICK_COUNT, + Math.max(BAR_CHART_CONSTANTS.MINIMUM_VALUE_TICK_COUNT, tickCount), ); const categoryTickValues = computeBarChartCategoryTickValues({ @@ -73,18 +72,29 @@ export const getBarChartTickConfig = ({ valueTickCount: numberOfValueTicks, }); - const maxBottomAxisTickLabelLength = calculateMaxTickLabelLength({ - widthPerTick, + const actualDataPointCount = data.length; + const widthPerDataPoint = + layout === BarChartLayout.VERTICAL && + actualDataPointCount > 0 && + availableWidth > 0 + ? availableWidth / actualDataPointCount + : widthPerTick; + + const tickRotationConfig = getTickRotationConfig({ + widthPerTick: widthPerDataPoint, axisFontSize, }); - // TODO: Make this dynamic based on the data - const maxLeftAxisTickLabelLength = MAX_LEFT_AXIS_LABEL_LENGTH; + const maxLeftAxisTickLabelLength = computeMaxLabelLengthForMargin({ + marginSize: margins.left, + axisFontSize, + }); return { categoryTickValues, numberOfValueTicks, - maxBottomAxisTickLabelLength, + maxBottomAxisTickLabelLength: tickRotationConfig.maxLabelLength, maxLeftAxisTickLabelLength, + bottomAxisTickRotation: tickRotationConfig.tickRotation, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts index a3e1a270d8..81602898db 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts @@ -3,7 +3,7 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataI import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { fillDateGapsInBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/fillDateGapsInBarChartData'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts index 3c262e962c..5eb17bf0f9 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformOneDimensionalGroupByToBarChartData.ts @@ -2,7 +2,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { GRAPH_DEFAULT_COLOR } from '@/page-layout/widgets/graph/constants/GraphDefaultColor.constant'; -import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult'; @@ -56,7 +56,10 @@ export const transformOneDimensionalGroupByToBarChartData = ({ : aggregateField.name; // TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600) - const limitedResults = rawResults.slice(0, BAR_CHART_MAXIMUM_NUMBER_OF_BARS); + const limitedResults = rawResults.slice( + 0, + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS, + ); const formattedValues = formatPrimaryDimensionValues({ groupByRawResults: limitedResults, @@ -119,7 +122,8 @@ export const transformOneDimensionalGroupByToBarChartData = ({ indexBy: indexByKey, keys: [aggregateValueKey], series, - hasTooManyGroups: rawResults.length > BAR_CHART_MAXIMUM_NUMBER_OF_BARS, + hasTooManyGroups: + rawResults.length > BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS, formattedToRawLookup, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts index cb7d1aa4fb..f715a94039 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformTwoDimensionalGroupByToBarChartData.ts @@ -1,7 +1,7 @@ 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 { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; +import { BAR_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartConstants'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { sortBarChartDataBySecondaryDimensionSum } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/sortBarChartDataBySecondaryDimensionSum'; import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; @@ -97,7 +97,10 @@ export const transformTwoDimensionalGroupByToBarChartData = ({ const isNewY = !yValues.has(yValue); if (configuration.groupMode === BarChartGroupMode.STACKED) { - if (isNewX && xValues.size >= BAR_CHART_MAXIMUM_NUMBER_OF_BARS) { + if ( + isNewX && + xValues.size >= BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS + ) { hasTooManyGroups = true; return; } @@ -110,7 +113,7 @@ export const transformTwoDimensionalGroupByToBarChartData = ({ if ( totalUniqueDimensions + additionalDimensions > - BAR_CHART_MAXIMUM_NUMBER_OF_BARS + BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS ) { hasTooManyGroups = true; return; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/truncateTickLabel.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/truncateTickLabel.ts index 5871a287b7..e596db0b41 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/truncateTickLabel.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/truncateTickLabel.ts @@ -1,10 +1,18 @@ +import { ELLIPSIS_LENGTH } from '@/page-layout/widgets/graph/utils/ellipsisLength'; + export const truncateTickLabel = ( value: string | number, maxLength: number, ): string => { const stringValue = String(value); - if (maxLength < 4 || stringValue.length <= maxLength) { + + if (stringValue.length <= maxLength) { return stringValue; } - return `${stringValue.slice(0, maxLength - 3)}...`; + + if (maxLength <= ELLIPSIS_LENGTH) { + return '...'.slice(0, maxLength); + } + + return `${stringValue.slice(0, maxLength - ELLIPSIS_LENGTH)}...`; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/CustomCrosshairLayer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/CustomCrosshairLayer.tsx index 71dcde63d0..d3ad1bff27 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/CustomCrosshairLayer.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/CustomCrosshairLayer.tsx @@ -1,10 +1,4 @@ -import { LINE_CHART_CROSSHAIR_DASH_ARRAY } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairDashArray'; -import { LINE_CHART_CROSSHAIR_STROKE_OPACITY } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairStrokeOpacity'; -import { LINE_CHART_CROSSHAIR_STROKE_WIDTH } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairStrokeWidth'; -import { LINE_CHART_CROSSHAIR_TRANSITION_DAMPING } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairTransitionDamping'; -import { LINE_CHART_CROSSHAIR_TRANSITION_STIFFNESS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartCrosshairTransitionStiffness'; -import { LINE_CHART_MARGIN_LEFT } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginLeft'; -import { LINE_CHART_MARGIN_TOP } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginTop'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { graphWidgetLineCrosshairXComponentState } from '@/page-layout/widgets/graph/graphWidgetLineChart/states/graphWidgetLineCrosshairXComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { useTheme } from '@emotion/react'; @@ -29,6 +23,8 @@ type CustomCrosshairLayerProps = { points: readonly Point[]; innerHeight: number; innerWidth: number; + marginLeft: number; + marginTop: number; onSliceHover: (data: SliceHoverData) => void; onSliceClick?: (data: SliceHoverData) => void; onRectLeave: (relatedTarget: EventTarget | null) => void; @@ -38,6 +34,8 @@ export const CustomCrosshairLayer = ({ points, innerHeight, innerWidth, + marginLeft, + marginTop, onSliceHover, onSliceClick, onRectLeave, @@ -75,8 +73,8 @@ export const CustomCrosshairLayer = ({ return null; } - const mouseX = event.clientX - svgRect.left - LINE_CHART_MARGIN_LEFT; - const mouseY = event.clientY - svgRect.top - LINE_CHART_MARGIN_TOP; + const mouseX = event.clientX - svgRect.left - marginLeft; + const mouseY = event.clientY - svgRect.top - marginTop; const nearestSlice = slices.reduce((nearest, slice) => { const currentDistance = Math.abs(slice.x - mouseX); @@ -102,7 +100,7 @@ export const CustomCrosshairLayer = ({ svgRect, }; }, - [slices], + [marginLeft, marginTop, slices], ); const handleMouseMove = (event: MouseEvent) => { @@ -136,8 +134,8 @@ export const CustomCrosshairLayer = ({ const transition = { type: 'spring', - stiffness: LINE_CHART_CROSSHAIR_TRANSITION_STIFFNESS, - damping: LINE_CHART_CROSSHAIR_TRANSITION_DAMPING, + stiffness: LINE_CHART_CONSTANTS.CROSSHAIR_TRANSITION_STIFFNESS, + damping: LINE_CHART_CONSTANTS.CROSSHAIR_TRANSITION_DAMPING, } as const; return ( @@ -149,9 +147,9 @@ export const CustomCrosshairLayer = ({ y1={0} y2={innerHeight} stroke={theme.font.color.primary} - strokeWidth={LINE_CHART_CROSSHAIR_STROKE_WIDTH} - strokeOpacity={LINE_CHART_CROSSHAIR_STROKE_OPACITY} - strokeDasharray={LINE_CHART_CROSSHAIR_DASH_ARRAY} + strokeWidth={LINE_CHART_CONSTANTS.CROSSHAIR_STROKE_WIDTH} + strokeOpacity={LINE_CHART_CONSTANTS.CROSSHAIR_STROKE_OPACITY} + strokeDasharray={LINE_CHART_CONSTANTS.CROSSHAIR_DASH_ARRAY} initial={{ x1: crosshairX, x2: crosshairX, opacity: 0 }} animate={{ x1: crosshairX, x2: crosshairX, opacity: 0.5 }} exit={{ opacity: 0 }} diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphLineChartTooltip.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphLineChartTooltip.tsx index e34b7bd507..61d697f8b5 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphLineChartTooltip.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/components/GraphLineChartTooltip.tsx @@ -1,5 +1,5 @@ import { GraphWidgetFloatingTooltip } from '@/page-layout/widgets/graph/components/GraphWidgetFloatingTooltip'; -import { LINE_CHART_TOOLTIP_OFFSET_PX } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartTooltipOffsetPx'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { graphWidgetLineTooltipComponentState } from '@/page-layout/widgets/graph/graphWidgetLineChart/states/graphWidgetLineTooltipComponentState'; import { type LineChartEnrichedSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartEnrichedSeries'; import { getLineChartTooltipData } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartTooltipData'; @@ -71,7 +71,7 @@ export const GraphLineChartTooltip = ({ ; type LinesLayerProps = LineCustomSvgLayerProps; type NoDataLayerWrapperProps = LineCustomSvgLayerProps; -const LINE_CHART_DEFAULT_TICK_COUNT = 5; - type GraphWidgetLineChartProps = { data: LineChartSeries[]; showLegend?: boolean; @@ -171,8 +167,8 @@ export const GraphWidgetLineChart = ({ points: sliceData.nearestSlice.points, }; - const offsetLeft = sliceData.nearestSlice.x + LINE_CHART_MARGIN_LEFT; - const offsetTop = sliceData.mouseY + LINE_CHART_MARGIN_TOP; + const offsetLeft = sliceData.nearestSlice.x + marginLeft; + const offsetTop = sliceData.mouseY + COMMON_CHART_CONSTANTS.MARGIN_TOP; debouncedHideTooltip.cancel(); setCrosshairX(sliceData.sliceX); @@ -212,6 +208,8 @@ export const GraphWidgetLineChart = ({ points={layerProps.points} innerHeight={layerProps.innerHeight} innerWidth={layerProps.innerWidth} + marginLeft={marginLeft} + marginTop={COMMON_CHART_CONSTANTS.MARGIN_TOP} onSliceHover={handleSliceEnter} onSliceClick={ isDefined(onSliceClick) @@ -262,29 +260,24 @@ export const GraphWidgetLineChart = ({ /> ); - const axisBottomConfig = getLineChartAxisBottomConfig( - xAxisLabel, - chartWidth, - visibleData, - ); - const chartMargins = { - top: LINE_CHART_MARGIN_TOP, - right: LINE_CHART_MARGIN_RIGHT, - bottom: LINE_CHART_MARGIN_BOTTOM, - left: LINE_CHART_MARGIN_LEFT, - }; + const marginLeft = isDefined(yAxisLabel) + ? COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITH_LABEL + : COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITHOUT_LABEL; + + const { config: axisBottomConfig, marginBottom } = + getLineChartAxisBottomConfig(xAxisLabel, chartWidth, data, marginLeft); + const { tickValues: valueTickValues, domain: valueDomain } = computeValueTickValues({ minimum: effectiveMinimumValue, maximum: effectiveMaximumValue, - tickCount: LINE_CHART_DEFAULT_TICK_COUNT, + tickCount: LINE_CHART_CONSTANTS.DEFAULT_TICK_COUNT, }); const axisLeftConfig = getLineChartAxisLeftConfig( yAxisLabel, formatOptions, valueTickValues, - chartMargins.left, ); return ( @@ -303,10 +296,10 @@ export const GraphWidgetLineChart = ({ { } as LineChartConfiguration); expect(result).toBe( - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS + + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, ); }); @@ -27,8 +25,8 @@ describe('getLineChartQueryLimit', () => { } as LineChartConfiguration); expect(result).toBe( - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS * - LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES + + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS * + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_STACKED_SERIES + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, ); }); @@ -41,8 +39,8 @@ describe('getLineChartQueryLimit', () => { } as LineChartConfiguration); expect(result).toBe( - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS * - LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES + + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS * + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_NON_STACKED_SERIES + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, ); }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/computeLineChartCategoryTickValues.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/computeLineChartCategoryTickValues.ts index a7587b9f01..c1a489d412 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/computeLineChartCategoryTickValues.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/computeLineChartCategoryTickValues.ts @@ -1,16 +1,17 @@ +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries'; import { computeChartCategoryTickValues } from '@/page-layout/widgets/graph/utils/computeChartCategoryTickValues'; -const LINE_CHART_MARGIN_LEFT = 70; -const LINE_CHART_MARGIN_RIGHT = 20; -const LINE_CHART_MINIMUM_WIDTH_PER_TICK = 100; - export const computeLineChartCategoryTickValues = ({ width, data, + marginLeft, + marginRight, }: { width: number; data: LineChartSeries[]; + marginLeft: number; + marginRight: number; }): (string | number)[] => { if (data.length === 0 || data[0].data.length === 0) { return []; @@ -23,12 +24,15 @@ export const computeLineChartCategoryTickValues = ({ : String(value); }); - const availableWidth = - width - (LINE_CHART_MARGIN_LEFT + LINE_CHART_MARGIN_RIGHT); + const availableWidth = width - (marginLeft + marginRight); + + const dataPointCount = data[0].data.length; + const widthPerTick = dataPointCount > 0 ? availableWidth / dataPointCount : 0; return computeChartCategoryTickValues({ availableSize: availableWidth, - minimumSizePerTick: LINE_CHART_MINIMUM_WIDTH_PER_TICK, + minimumSizePerTick: LINE_CHART_CONSTANTS.MINIMUM_WIDTH_PER_TICK_ROTATED, values, + widthPerTick, }); }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/createAreaFillDef.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/createAreaFillDef.ts index 0f270558c0..89e918a77b 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/createAreaFillDef.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/createAreaFillDef.ts @@ -1,5 +1,4 @@ -import { LINE_AREA_FILL_END_OPACITY } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineAreaFillEndOpacity'; -import { LINE_AREA_FILL_START_OPACITY } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineAreaFillStartOpacity'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphColorScheme'; export const createAreaFillDef = ( @@ -17,12 +16,12 @@ export const createAreaFillDef = ( { offset: 0, color: colorScheme.solid, - opacity: LINE_AREA_FILL_START_OPACITY, + opacity: LINE_CHART_CONSTANTS.AREA_FILL_START_OPACITY, }, { offset: 100, color: colorScheme.solid, - opacity: LINE_AREA_FILL_END_OPACITY, + opacity: LINE_CHART_CONSTANTS.AREA_FILL_END_OPACITY, }, ], }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisBottomConfig.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisBottomConfig.ts index 4c11ae7bbe..0eab6188c8 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisBottomConfig.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisBottomConfig.ts @@ -1,23 +1,87 @@ +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; +import { truncateTickLabel } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/truncateTickLabel'; import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries'; import { computeLineChartCategoryTickValues } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/computeLineChartCategoryTickValues'; +import { getTickRotationConfig } from '@/page-layout/widgets/graph/utils/getTickRotationConfig'; +import { isNonEmptyArray } from '@sniptt/guards'; +import { isDefined } from 'twenty-shared/utils'; + +export type LineChartAxisBottomResult = { + config: { + tickSize: number; + tickPadding: number; + tickRotation: number; + tickValues: (string | number)[] | undefined; + legend: string | undefined; + legendPosition: 'middle'; + legendOffset: number; + format: (value: string | number) => string; + }; + marginBottom: number; +}; export const getLineChartAxisBottomConfig = ( xAxisLabel?: string, width?: number, data?: LineChartSeries[], -) => { + marginLeft?: number, +): LineChartAxisBottomResult => { + const effectiveMarginLeft = + marginLeft ?? COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITH_LABEL; + const tickValues = width && data - ? computeLineChartCategoryTickValues({ width, data }) + ? computeLineChartCategoryTickValues({ + width, + data, + marginLeft: effectiveMarginLeft, + marginRight: COMMON_CHART_CONSTANTS.MARGIN_RIGHT, + }) : undefined; + const availableWidth = width + ? width - (effectiveMarginLeft + COMMON_CHART_CONSTANTS.MARGIN_RIGHT) + : 0; + + const actualDataPointCount = isNonEmptyArray(data?.[0]?.data) + ? data[0].data.length + : 1; + + const widthPerDataPoint = + actualDataPointCount > 0 && availableWidth > 0 + ? availableWidth / actualDataPointCount + : 0; + + const { tickRotation, maxLabelLength } = getTickRotationConfig({ + widthPerTick: widthPerDataPoint, + axisFontSize: COMMON_CHART_CONSTANTS.AXIS_FONT_SIZE, + }); + + const hasRotation = tickRotation !== 0; + const baseMarginBottom = isDefined(xAxisLabel) + ? COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITH_LABEL + : COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITHOUT_LABEL; + const marginBottom = hasRotation + ? baseMarginBottom + + COMMON_CHART_CONSTANTS.ROTATED_LABELS_EXTRA_BOTTOM_MARGIN + : baseMarginBottom; + return { - tickSize: 0, - tickPadding: 5, - tickRotation: 0, - tickValues, - legend: xAxisLabel, - legendPosition: 'middle' as const, - legendOffset: 40, + config: { + tickSize: COMMON_CHART_CONSTANTS.TICK_SIZE, + tickPadding: COMMON_CHART_CONSTANTS.TICK_PADDING, + tickRotation, + tickValues, + legend: xAxisLabel, + legendPosition: 'middle' as const, + legendOffset: + COMMON_CHART_CONSTANTS.BOTTOM_AXIS_LEGEND_OFFSET + + (hasRotation + ? COMMON_CHART_CONSTANTS.ROTATED_LABELS_EXTRA_BOTTOM_MARGIN + : 0), + format: (value: string | number) => + truncateTickLabel(String(value), maxLabelLength), + }, + marginBottom, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisLeftConfig.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisLeftConfig.ts index 57a555ddef..f34a902440 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisLeftConfig.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartAxisLeftConfig.ts @@ -1,24 +1,32 @@ -import { LINE_CHART_MARGIN_LEFT } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMarginLeft'; +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; import { formatGraphValue, type GraphValueFormatOptions, } from '@/page-layout/widgets/graph/utils/graphFormatters'; - -const LEFT_AXIS_LEGEND_OFFSET_PADDING = 5; -const TICK_PADDING = 5; +import { isDefined } from 'twenty-shared/utils'; export const getLineChartAxisLeftConfig = ( yAxisLabel?: string, formatOptions?: GraphValueFormatOptions, tickValues?: number[], - marginLeft: number = LINE_CHART_MARGIN_LEFT, -) => ({ - tickSize: 0, - tickPadding: TICK_PADDING, - tickRotation: 0, - tickValues, - legend: yAxisLabel, - legendPosition: 'middle' as const, - legendOffset: -marginLeft + LEFT_AXIS_LEGEND_OFFSET_PADDING, - format: (value: number) => formatGraphValue(value, formatOptions || {}), -}); + marginLeft?: number, +) => { + const effectiveMarginLeft = + marginLeft ?? + (isDefined(yAxisLabel) + ? COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITH_LABEL + : COMMON_CHART_CONSTANTS.MARGIN_LEFT_WITHOUT_LABEL); + + return { + tickSize: COMMON_CHART_CONSTANTS.TICK_SIZE, + tickPadding: COMMON_CHART_CONSTANTS.TICK_PADDING, + tickRotation: COMMON_CHART_CONSTANTS.NO_ROTATION_ANGLE, + tickValues, + legend: yAxisLabel, + legendPosition: 'middle' as const, + legendOffset: + -effectiveMarginLeft + + COMMON_CHART_CONSTANTS.LEFT_AXIS_LEGEND_OFFSET_PADDING, + format: (value: number) => formatGraphValue(value, formatOptions || {}), + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit.ts index c270a93677..2fa0cad6e2 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit.ts @@ -1,7 +1,5 @@ -import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant'; -import { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant'; -import { LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfNonStackedSeries.constant'; -import { LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfStackedSeries.constant'; +import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { isChartConfigurationTwoDimensional } from '@/page-layout/widgets/graph/utils/isChartConfigurationTwoDimensional'; import { type LineChartConfiguration } from '~/generated/graphql'; @@ -12,22 +10,22 @@ export const getLineChartQueryLimit = ( if (!isTwoDimensional) { return ( - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS + + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS ); } if (configuration.isStacked === true) { return ( - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS * - LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES + + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS * + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_STACKED_SERIES + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS ); } return ( - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS * - LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES + + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS * + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_NON_STACKED_SERIES + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS ); }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts index bf91246099..fc757b2a56 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts @@ -1,7 +1,7 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant'; +import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups'; import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; import { transformGroupByDataToPieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeChartCategoryTickValues.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeChartCategoryTickValues.test.ts index c67bc458cb..492a44ddae 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeChartCategoryTickValues.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeChartCategoryTickValues.test.ts @@ -1,3 +1,4 @@ +import { ROTATION_THRESHOLD_WIDTH } from '@/page-layout/widgets/graph/constants/RotationThresholdWidth'; import { computeChartCategoryTickValues } from '../computeChartCategoryTickValues'; describe('computeChartCategoryTickValues', () => { @@ -89,4 +90,60 @@ describe('computeChartCategoryTickValues', () => { expect(result).toEqual([]); }); + + describe('with widthPerTick parameter', () => { + it('should return all values when widthPerTick is above rotation threshold', () => { + const values = ['A', 'B', 'C', 'D', 'E']; + const result = computeChartCategoryTickValues({ + availableSize: 200, + minimumSizePerTick: 100, + values, + widthPerTick: ROTATION_THRESHOLD_WIDTH + 10, + }); + + expect(result).toEqual(['A', 'B', 'C', 'D', 'E']); + }); + + it('should return all values when rotated and widthPerTick exceeds minimumSizePerTick', () => { + const values = ['A', 'B', 'C', 'D', 'E']; + const result = computeChartCategoryTickValues({ + availableSize: 200, + minimumSizePerTick: 20, + values, + widthPerTick: 30, + }); + + expect(result).toEqual(['A', 'B', 'C', 'D', 'E']); + }); + + it('should omit ticks when rotated and widthPerTick is below minimumSizePerTick', () => { + const values = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; + const result = computeChartCategoryTickValues({ + availableSize: 400, + minimumSizePerTick: 100, + values, + widthPerTick: 10, + }); + + expect(result.length).toBe(4); + expect(result).toEqual(['A', 'C', 'F', 'H']); + }); + + it('should behave like no widthPerTick when widthPerTick is undefined', () => { + const values = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']; + const resultWithoutWidthPerTick = computeChartCategoryTickValues({ + availableSize: 400, + minimumSizePerTick: 100, + values, + }); + const resultWithUndefined = computeChartCategoryTickValues({ + availableSize: 400, + minimumSizePerTick: 100, + values, + widthPerTick: undefined, + }); + + expect(resultWithoutWidthPerTick).toEqual(resultWithUndefined); + }); + }); }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeMaxLabelLengthForMargin.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeMaxLabelLengthForMargin.test.ts new file mode 100644 index 0000000000..536d76d985 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/computeMaxLabelLengthForMargin.test.ts @@ -0,0 +1,70 @@ +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; +import { computeMaxLabelLengthForMargin } from '../computeMaxLabelLengthForMargin'; + +describe('computeMaxLabelLengthForMargin', () => { + it('should calculate label length based on margin size and font size', () => { + const result = computeMaxLabelLengthForMargin({ + marginSize: 100, + axisFontSize: 11, + }); + + const expectedCharacterWidth = + 11 * COMMON_CHART_CONSTANTS.HORIZONTAL_LABEL_CHARACTER_WIDTH_RATIO; + const availableWidth = 100 - COMMON_CHART_CONSTANTS.TICK_PADDING_ALLOWANCE; + const expectedLength = Math.floor(availableWidth / expectedCharacterWidth); + + expect(result).toBe(expectedLength); + }); + + it('should return minimum length when margin is too small', () => { + const result = computeMaxLabelLengthForMargin({ + marginSize: 20, + axisFontSize: 11, + }); + + const minimumLength = + COMMON_CHART_CONSTANTS.TICK_MINIMUM_NUMBER_OF_DISPLAYED_CHARACTERS; + + expect(result).toBe(minimumLength); + }); + + it('should scale with font size', () => { + const smallFont = computeMaxLabelLengthForMargin({ + marginSize: 100, + axisFontSize: 10, + }); + + const largeFont = computeMaxLabelLengthForMargin({ + marginSize: 100, + axisFontSize: 14, + }); + + expect(smallFont).toBeGreaterThan(largeFont); + }); + + it('should scale with margin size', () => { + const smallMargin = computeMaxLabelLengthForMargin({ + marginSize: 50, + axisFontSize: 11, + }); + + const largeMargin = computeMaxLabelLengthForMargin({ + marginSize: 150, + axisFontSize: 11, + }); + + expect(largeMargin).toBeGreaterThan(smallMargin); + }); + + it('should never return less than the minimum displayable characters', () => { + const result = computeMaxLabelLengthForMargin({ + marginSize: 0, + axisFontSize: 11, + }); + + const minimumLength = + COMMON_CHART_CONSTANTS.TICK_MINIMUM_NUMBER_OF_DISPLAYED_CHARACTERS; + + expect(result).toBeGreaterThanOrEqual(minimumLength); + }); +}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildGroupByFieldObject.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildGroupByFieldObject.ts index cfc711edc6..00403c1e1c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildGroupByFieldObject.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/buildGroupByFieldObject.ts @@ -2,7 +2,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { CalendarStartDay } from 'twenty-shared'; import { type FirstDayOfTheWeek, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeAggregateNumericValueForGraph.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeAggregateNumericValueForGraph.ts index 6cd09a247e..c9bcb22613 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeAggregateNumericValueForGraph.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeAggregateNumericValueForGraph.ts @@ -4,7 +4,7 @@ import { type AggregateOperations } from '@/object-record/record-table/constants import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions'; import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; -import { GRAPH_DEFAULT_AGGREGATE_VALUE } from '@/page-layout/widgets/graph/constants/GraphDefaultAggregateValue.constant'; +import { GRAPH_DEFAULT_AGGREGATE_VALUE } from '@/page-layout/widgets/graph/constants/GraphDefaultAggregateValue'; import isEmpty from 'lodash.isempty'; import { isDefined } from 'twenty-shared/utils'; import { FieldMetadataType } from '~/generated-metadata/graphql'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeChartCategoryTickValues.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeChartCategoryTickValues.ts index 8da2a8830b..32389ded6a 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeChartCategoryTickValues.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeChartCategoryTickValues.ts @@ -1,18 +1,31 @@ +import { ROTATION_THRESHOLD_WIDTH } from '@/page-layout/widgets/graph/constants/RotationThresholdWidth'; import { computeCategoryTickValues } from '@/page-layout/widgets/graph/utils/computeCategoryTickValues'; +import { isDefined } from 'twenty-shared/utils'; export const computeChartCategoryTickValues = ({ availableSize, minimumSizePerTick, values, + widthPerTick, }: { availableSize: number; minimumSizePerTick: number; values: (string | number)[]; + widthPerTick?: number; }): (string | number)[] => { if (availableSize <= 0 || values.length === 0) { return []; } + if (isDefined(widthPerTick)) { + const willRotate = widthPerTick < ROTATION_THRESHOLD_WIDTH; + const shouldOmitTicks = willRotate && widthPerTick < minimumSizePerTick; + + if (!shouldOmitTicks) { + return values; + } + } + const numberOfTicks = Math.floor(availableSize / minimumSizePerTick); const tickIndices = computeCategoryTickValues(numberOfTicks, values.length); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeEffectiveValueRange.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeEffectiveValueRange.ts index 970268a336..0fa093651d 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeEffectiveValueRange.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeEffectiveValueRange.ts @@ -1,8 +1,6 @@ +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; import { isDefined } from 'twenty-shared/utils'; -const POSITIVE_RANGE_PADDING_RATIO = 0.1; -const MINIMUM_POSITIVE_RANGE_PADDING = 1; - type ComputeEffectiveValueRangeParams = { calculatedMinimum: number; calculatedMaximum: number; @@ -39,8 +37,9 @@ export const computeEffectiveValueRange = ({ ? positiveRangePaddingTarget : positiveRangePaddingTarget + Math.max( - Math.abs(positiveRangePaddingTarget) * POSITIVE_RANGE_PADDING_RATIO, - MINIMUM_POSITIVE_RANGE_PADDING, + Math.abs(positiveRangePaddingTarget) * + COMMON_CHART_CONSTANTS.POSITIVE_RANGE_PADDING_RATIO, + COMMON_CHART_CONSTANTS.MINIMUM_POSITIVE_RANGE_PADDING, ); let effectiveMinimumValue = baseMinimumValue; @@ -49,7 +48,8 @@ export const computeEffectiveValueRange = ({ if (!isDefined(rangeMax) && !isDefined(rangeMin)) { if (effectiveMinimumValue === effectiveMaximumValue) { effectiveMaximumValue = - effectiveMinimumValue + MINIMUM_POSITIVE_RANGE_PADDING; + effectiveMinimumValue + + COMMON_CHART_CONSTANTS.MINIMUM_POSITIVE_RANGE_PADDING; } } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeMaxLabelLengthForMargin.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeMaxLabelLengthForMargin.ts new file mode 100644 index 0000000000..5f1e22f920 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/computeMaxLabelLengthForMargin.ts @@ -0,0 +1,21 @@ +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; + +export const computeMaxLabelLengthForMargin = ({ + marginSize, + axisFontSize, +}: { + marginSize: number; + axisFontSize: number; +}): number => { + const characterWidth = + axisFontSize * + COMMON_CHART_CONSTANTS.HORIZONTAL_LABEL_CHARACTER_WIDTH_RATIO; + const availableWidth = + marginSize - COMMON_CHART_CONSTANTS.TICK_PADDING_ALLOWANCE; + const calculatedLength = Math.max( + COMMON_CHART_CONSTANTS.TICK_MINIMUM_NUMBER_OF_DISPLAYED_CHARACTERS, + Math.floor(availableWidth / characterWidth), + ); + + return calculatedLength; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/ellipsisLength.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/ellipsisLength.ts new file mode 100644 index 0000000000..abb43125cc --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/ellipsisLength.ts @@ -0,0 +1 @@ +export const ELLIPSIS_LENGTH = 3; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts index 2618c12a74..cf14cd7f39 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts @@ -1,5 +1,5 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { formatDateByGranularity } from '@/page-layout/widgets/graph/utils/formatDateByGranularity'; import { t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts index 7dbd6477f5..3e8016735b 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts @@ -1,5 +1,5 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { GRAPH_DEFAULT_ORDER_BY } from '@/page-layout/widgets/graph/constants/GraphDefaultOrderBy'; import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy'; import { isRelationNestedFieldDateKind } from '@/page-layout/widgets/graph/utils/isRelationNestedFieldDateKind'; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts index 5d54856ce6..ad45b74e71 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts @@ -1,5 +1,5 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy'; import { isRelationNestedFieldDateKind } from '@/page-layout/widgets/graph/utils/isRelationNestedFieldDateKind'; import { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getFieldOrderBy.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getFieldOrderBy.ts index 282ff9e927..fe17fe5972 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getFieldOrderBy.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getFieldOrderBy.ts @@ -2,7 +2,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType'; import { isFieldMorphRelation } from '@/object-record/record-field/ui/types/guards/isFieldMorphRelation'; import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { getRelationFieldOrderBy } from '@/page-layout/widgets/graph/utils/getRelationFieldOrderBy'; import { type ObjectRecordGroupByDateGranularity, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getRelationFieldOrderBy.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getRelationFieldOrderBy.ts index d3a9b5a06b..9cc5f86cf3 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getRelationFieldOrderBy.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getRelationFieldOrderBy.ts @@ -1,5 +1,5 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; -import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant'; +import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity'; import { type ObjectRecordGroupByDateGranularity, type ObjectRecordOrderByForRelationField, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getTickRotationConfig.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getTickRotationConfig.ts new file mode 100644 index 0000000000..98b474a01b --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/getTickRotationConfig.ts @@ -0,0 +1,57 @@ +import { COMMON_CHART_CONSTANTS } from '@/page-layout/widgets/graph/constants/CommonChartConstants'; + +const TICK_ROTATION_ANGLE_RAD = + (Math.abs(COMMON_CHART_CONSTANTS.TICK_ROTATION_ANGLE) * Math.PI) / 180; +const MIN_CALCULATED_LENGTH = 1; +const TICK_MARGIN = 1; + +export type TickRotationConfig = { + tickRotation: number; + maxLabelLength: number; +}; + +export const getTickRotationConfig = ({ + widthPerTick, + axisFontSize, +}: { + widthPerTick: number; + axisFontSize: number; +}): TickRotationConfig => { + const shouldRotate = + widthPerTick < + COMMON_CHART_CONSTANTS.TICK_MINIMUM_NUMBER_OF_DISPLAYED_CHARACTERS * + COMMON_CHART_CONSTANTS.HORIZONTAL_LABEL_CHARACTER_WIDTH_RATIO * + axisFontSize + + TICK_MARGIN; + + if (shouldRotate) { + const characterWidth = + axisFontSize * COMMON_CHART_CONSTANTS.ROTATED_LABEL_CHARACTER_WIDTH_RATIO; + const calculatedLength = Math.max( + MIN_CALCULATED_LENGTH, + Math.floor( + COMMON_CHART_CONSTANTS.MARGIN_BOTTOM_WITHOUT_LABEL / + (characterWidth * Math.sin(TICK_ROTATION_ANGLE_RAD)), + ), + ); + + return { + tickRotation: COMMON_CHART_CONSTANTS.TICK_ROTATION_ANGLE, + maxLabelLength: calculatedLength, + }; + } + + const characterWidth = + axisFontSize * + COMMON_CHART_CONSTANTS.HORIZONTAL_LABEL_CHARACTER_WIDTH_RATIO; + + const calculatedLength = Math.max( + MIN_CALCULATED_LENGTH, + Math.floor(widthPerTick / characterWidth), + ); + + return { + tickRotation: COMMON_CHART_CONSTANTS.NO_ROTATION_ANGLE, + maxLabelLength: calculatedLength, + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformOneDimensionalGroupByToLineChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformOneDimensionalGroupByToLineChartData.ts index 66594abb24..1af4328c7a 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformOneDimensionalGroupByToLineChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformOneDimensionalGroupByToLineChartData.ts @@ -2,7 +2,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { GRAPH_DEFAULT_COLOR } from '@/page-layout/widgets/graph/constants/GraphDefaultColor.constant'; -import { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint'; import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries'; import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; @@ -44,7 +44,7 @@ export const transformOneDimensionalGroupByToLineChartData = ({ // TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600) const limitedResults = rawResults.slice( 0, - LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS, + LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS, ); const formattedValues = formatPrimaryDimensionValues({ @@ -112,7 +112,7 @@ export const transformOneDimensionalGroupByToLineChartData = ({ return { series, hasTooManyGroups: - rawResults.length > LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS, + rawResults.length > LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS, formattedToRawLookup, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToLineChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToLineChartData.ts index 5e9981a00e..60777b8c75 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToLineChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToLineChartData.ts @@ -1,7 +1,7 @@ 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 { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant'; +import { LINE_CHART_CONSTANTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartConstants'; import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint'; import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries'; import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; @@ -73,7 +73,10 @@ export const transformTwoDimensionalGroupByToLineChartData = ({ // TODO: Add a limit to the query instead of checking here (issue: twentyhq/core-team-issues#1600) const isNewX = !xValueSet.has(xValue); - if (isNewX && xValueSet.size >= LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS) { + if ( + isNewX && + xValueSet.size >= LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS + ) { hasTooManyGroups = true; return; }