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:
+20
-19
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user