Split bar graph into two distinct horizontal and vertical bars (#15061)

### Summary

Split BAR into VERTICAL_BAR and HORIZONTAL_BAR as separate chart types.

### The Problem

Initially wanted a simple vertical/horizontal toggle for bar charts, but
ran into a GraphQL union type
constraint: union types can't have the same field name with different
nullability.

- Vertical bars need groupByFieldMetadataIdX as required (categories on
X)
- Horizontal bars need groupByFieldMetadataIdY as required (categories
on Y)
  - GraphQL schema generation fails with this setup

### The Solution

Use semantic primaryAxis and secondaryAxis naming that's
orientation-agnostic:

- primaryAxisGroupByFieldMetadataId = main grouping field (e.g.,
"Company Name")
- secondaryAxisGroupByFieldMetadataId = optional secondary grouping
(e.g., "Stage")

These fields have consistent meaning regardless of orientation. The
visual mapping happens at the UI layer:
  - Vertical bars: primary data renders on X-axis, secondary on Y-axis
  - Horizontal bars: primary data renders on Y-axis, secondary on X-axis

Both chart types share the same DTO structure with consistent
nullability.

### What Changed

  - Split GraphType.BAR → VERTICAL_BAR | HORIZONTAL_BAR
- Renamed fields: primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId (+ subfield
  variants)
- useChartSettingsValues(): Maps semantic fields to setting values (no
swapping)
- getBarChartSettings(): Dynamically arranges settings panel based on
orientation
- transformGroupByDataToBarChartData(): Maps semantic fields to Nivo's
layout prop
  
  video QA
  

https://github.com/user-attachments/assets/479061b5-712e-4ca6-9858-95273d1f16c1
This commit is contained in:
nitin
2025-10-15 15:08:48 +05:30
committed by GitHub
parent d21be90c5d
commit 436084f8f4
48 changed files with 1453 additions and 412 deletions
@@ -2,6 +2,7 @@ import { isDefined } from 'twenty-shared/utils';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { AxisNameDisplay } from 'src/engine/core-modules/page-layout/enums/axis-name-display.enum';
import { GraphType } from 'src/engine/core-modules/page-layout/enums/graph-type.enum';
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PAGE_LAYOUT_TAB_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-tab-seeds.constant';
@@ -142,8 +143,8 @@ export const getPageLayoutWidgetDataSeeds = (
graphType: 'LINE',
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataIdX: opportunityCloseDateFieldId,
orderByX: 'FIELD_ASC',
primaryAxisGroupByFieldMetadataId: opportunityCloseDateFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
}
@@ -167,12 +168,12 @@ export const getPageLayoutWidgetDataSeeds = (
isDefined(opportunityCloseDateFieldId) &&
isDefined(opportunityStageFieldId)
? {
graphType: 'BAR',
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataIdX: opportunityCloseDateFieldId,
groupByFieldMetadataIdY: opportunityStageFieldId,
orderByX: 'FIELD_ASC',
primaryAxisGroupByFieldMetadataId: opportunityCloseDateFieldId,
secondaryAxisGroupByFieldMetadataId: opportunityStageFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.BOTH,
displayDataLabel: false,
}
@@ -196,11 +197,11 @@ export const getPageLayoutWidgetDataSeeds = (
configuration:
isDefined(rocketIdFieldId) && isDefined(rocketCreatedAtFieldId)
? {
graphType: 'BAR',
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: rocketIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: rocketCreatedAtFieldId,
orderByX: 'FIELD_ASC',
primaryAxisGroupByFieldMetadataId: rocketCreatedAtFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
}
@@ -271,8 +272,8 @@ export const getPageLayoutWidgetDataSeeds = (
graphType: 'LINE',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: companyCreatedAtFieldId,
orderByX: 'FIELD_ASC',
primaryAxisGroupByFieldMetadataId: companyCreatedAtFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
}
@@ -296,13 +297,13 @@ export const getPageLayoutWidgetDataSeeds = (
isDefined(companyEmployeesFieldId) &&
isDefined(companyAddressFieldId)
? {
graphType: 'BAR',
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: companyEmployeesFieldId,
groupByFieldMetadataIdY: companyAddressFieldId,
groupBySubFieldNameY: 'addressCity',
orderByX: 'FIELD_ASC',
primaryAxisGroupByFieldMetadataId: companyEmployeesFieldId,
secondaryAxisGroupByFieldMetadataId: companyAddressFieldId,
secondaryAxisGroupBySubFieldName: 'addressCity',
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.BOTH,
displayDataLabel: false,
}
@@ -463,11 +464,11 @@ export const getPageLayoutWidgetDataSeeds = (
configuration:
isDefined(personIdFieldId) && isDefined(personCityFieldId)
? {
graphType: 'BAR',
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: personIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: personCityFieldId,
orderByX: 'VALUE_DESC',
primaryAxisGroupByFieldMetadataId: personCityFieldId,
primaryAxisOrderBy: 'VALUE_DESC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
}