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:
+13
-13
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
+8
-2
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user