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
@@ -50,11 +50,11 @@ describe('extractFieldMetadataIdsFromWidget', () => {
const widget = createMockWidget({
configuration: {
__typename: 'BarChartConfiguration' as const,
graphType: GraphType.BAR,
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: 'field-1',
aggregateOperation: ExtendedAggregateOperations.COUNT,
groupByFieldMetadataIdX: 'field-2',
orderByX: GraphOrderBy.FIELD_ASC,
primaryAxisGroupByFieldMetadataId: 'field-2',
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
axisNameDisplay: AxisNameDisplay.BOTH,
displayDataLabel: false,
},
@@ -72,12 +72,12 @@ describe('extractFieldMetadataIdsFromWidget', () => {
const widget = createMockWidget({
configuration: {
__typename: 'BarChartConfiguration' as const,
graphType: GraphType.BAR,
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: 'field-1',
aggregateOperation: ExtendedAggregateOperations.COUNT,
groupByFieldMetadataIdX: 'field-2',
groupByFieldMetadataIdY: 'field-3',
orderByX: GraphOrderBy.FIELD_ASC,
primaryAxisGroupByFieldMetadataId: 'field-2',
secondaryAxisGroupByFieldMetadataId: 'field-3',
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
axisNameDisplay: AxisNameDisplay.BOTH,
displayDataLabel: false,
},
@@ -99,8 +99,8 @@ describe('extractFieldMetadataIdsFromWidget', () => {
graphType: GraphType.LINE,
aggregateFieldMetadataId: 'field-1',
aggregateOperation: ExtendedAggregateOperations.SUM,
groupByFieldMetadataIdX: 'field-2',
orderByX: GraphOrderBy.FIELD_DESC,
primaryAxisGroupByFieldMetadataId: 'field-2',
primaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
axisNameDisplay: AxisNameDisplay.BOTH,
displayDataLabel: false,
},
@@ -175,12 +175,12 @@ describe('extractFieldMetadataIdsFromWidget', () => {
const widget = createMockWidget({
configuration: {
__typename: 'BarChartConfiguration' as const,
graphType: GraphType.BAR,
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: 'field-1',
aggregateOperation: ExtendedAggregateOperations.COUNT,
groupByFieldMetadataIdX: 'field-2',
groupByFieldMetadataIdY: undefined,
orderByX: GraphOrderBy.FIELD_ASC,
primaryAxisGroupByFieldMetadataId: 'field-2',
secondaryAxisGroupByFieldMetadataId: undefined,
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
axisNameDisplay: AxisNameDisplay.BOTH,
displayDataLabel: false,
},
@@ -35,7 +35,7 @@ const createDefaultGraphConfiguration = (
case GraphType.PIE:
return null;
case GraphType.BAR:
case GraphType.VERTICAL_BAR:
if (
!isDefined(fieldSelection?.aggregateFieldMetadataId) ||
!isDefined(fieldSelection?.groupByFieldMetadataIdX)
@@ -44,13 +44,34 @@ const createDefaultGraphConfiguration = (
}
return {
__typename: 'BarChartConfiguration',
graphType: GraphType.BAR,
graphType: GraphType.VERTICAL_BAR,
displayDataLabel: false,
color: 'blue' satisfies ThemeColor,
groupByFieldMetadataIdX: fieldSelection.groupByFieldMetadataIdX,
primaryAxisGroupByFieldMetadataId:
fieldSelection.groupByFieldMetadataIdX,
aggregateFieldMetadataId: fieldSelection.aggregateFieldMetadataId,
aggregateOperation: ExtendedAggregateOperations.SUM,
orderByX: GraphOrderBy.FIELD_ASC,
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
axisNameDisplay: AxisNameDisplay.BOTH,
};
case GraphType.HORIZONTAL_BAR:
if (
!isDefined(fieldSelection?.aggregateFieldMetadataId) ||
!isDefined(fieldSelection?.groupByFieldMetadataIdX)
) {
return null;
}
return {
__typename: 'BarChartConfiguration',
graphType: GraphType.HORIZONTAL_BAR,
displayDataLabel: false,
color: 'blue' satisfies ThemeColor,
primaryAxisGroupByFieldMetadataId:
fieldSelection.groupByFieldMetadataIdX,
aggregateFieldMetadataId: fieldSelection.aggregateFieldMetadataId,
aggregateOperation: ExtendedAggregateOperations.SUM,
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
axisNameDisplay: AxisNameDisplay.BOTH,
};
@@ -15,11 +15,17 @@ export const extractFieldMetadataIdsFromWidget = (
switch (config.__typename) {
case 'BarChartConfiguration':
return [
config.aggregateFieldMetadataId,
config.primaryAxisGroupByFieldMetadataId,
config.secondaryAxisGroupByFieldMetadataId,
].filter(isDefined);
case 'LineChartConfiguration':
return [
config.aggregateFieldMetadataId,
config.groupByFieldMetadataIdX,
config.groupByFieldMetadataIdY,
config.primaryAxisGroupByFieldMetadataId,
config.secondaryAxisGroupByFieldMetadataId,
].filter(isDefined);
case 'PieChartConfiguration':
@@ -26,7 +26,7 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
],
};
case GraphType.BAR:
case GraphType.VERTICAL_BAR:
return {
items: [
{ category: 'Jan', value: 45 },
@@ -41,6 +41,21 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
layout: 'vertical' as const,
};
case GraphType.HORIZONTAL_BAR:
return {
items: [
{ category: 'Jan', value: 45 },
{ category: 'Feb', value: 52 },
{ category: 'Mar', value: 48 },
{ category: 'Apr', value: 61 },
{ category: 'May', value: 55 },
],
indexBy: 'category',
keys: ['value'],
seriesLabels: { value: 'Value' },
layout: 'horizontal' as const,
};
case GraphType.LINE:
return {
series: [
@@ -119,7 +134,8 @@ export const getWidgetTitle = (graphType: GraphType, index: number): string => {
[GraphType.NUMBER]: 'Number',
[GraphType.GAUGE]: 'Gauge',
[GraphType.PIE]: 'Pie Chart',
[GraphType.BAR]: 'Bar Chart',
[GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
[GraphType.HORIZONTAL_BAR]: 'Horizontal Bar Chart',
[GraphType.LINE]: 'Line Chart',
};
@@ -134,7 +150,8 @@ export const getWidgetSize = (graphType: GraphType) => {
return { w: 3, h: 3 };
case GraphType.PIE:
return { w: 4, h: 4 };
case GraphType.BAR:
case GraphType.VERTICAL_BAR:
case GraphType.HORIZONTAL_BAR:
return { w: 6, h: 6 };
case GraphType.LINE:
return { w: 6, h: 10 };