Design adjustments on Bar chart (#15028)

- Make the ticks dynamic when we resize the graph
- Fix formatting
- Fix gradient color not working when the index have a space in it
- Fix the maximum number of groups for a grouped by graph
- Update the tooltip design and display the group

Video:


https://github.com/user-attachments/assets/9f304b6c-3dec-4ce2-9127-41d27f393d90

---------

Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
This commit is contained in:
Raphaël Bosi
2025-10-13 11:43:06 +02:00
committed by GitHub
parent 6188c72f74
commit 168e7b16ec
22 changed files with 351 additions and 113 deletions
@@ -61,7 +61,11 @@ export const computeAggregateNumericValueForGraph = ({
}
switch (field.type) {
case FieldMetadataType.CURRENCY:
case FieldMetadataType.CURRENCY: {
// Convert from micros (millionths) to actual currency amount
return Number(aggregateValue) / 1_000_000;
}
case FieldMetadataType.NUMBER: {
return Number(aggregateValue);
}
@@ -2,16 +2,19 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
import { formatDateByGranularity } from '@/page-layout/widgets/graph/utils/formatDateByGranularity';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import {
FieldMetadataType,
ObjectRecordGroupByDateGranularity,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { formatToShortNumber } from '~/utils/format/formatToShortNumber';
type FormatDimensionValueParams = {
value: unknown;
fieldMetadata: FieldMetadataItem;
dateGranularity?: ObjectRecordGroupByDateGranularity;
subFieldName?: string;
};
const normalizeMultiSelectValue = (value: unknown): unknown[] => {
@@ -39,9 +42,10 @@ export const formatDimensionValue = ({
value,
fieldMetadata,
dateGranularity = GRAPH_DEFAULT_DATE_GRANULARITY as ObjectRecordGroupByDateGranularity,
subFieldName,
}: FormatDimensionValueParams): string => {
if (!isDefined(value)) {
return '';
return t`Not Set`;
}
switch (fieldMetadata.type) {
@@ -85,6 +89,25 @@ export const formatDimensionValue = ({
return formatDateByGranularity(new Date(String(value)), dateGranularity);
}
case FieldMetadataType.NUMBER:
case FieldMetadataType.CURRENCY: {
if (
fieldMetadata.type === FieldMetadataType.CURRENCY &&
subFieldName === 'currencyCode'
) {
if (!isNonEmptyString(value)) {
return t`Not Set`;
}
return String(value);
}
const numericValue = typeof value === 'number' ? value : Number(value);
if (isNaN(numericValue)) {
return String(value);
}
return formatToShortNumber(numericValue);
}
default:
return String(value);
}
@@ -2,7 +2,6 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { getGroupByQueryName } from '@/page-layout/utils/getGroupByQueryName';
import { GRAPH_MAXIMUM_NUMBER_OF_GROUPS } from '@/page-layout/widgets/graph/constants/GraphMaximumNumberOfGroups.constant';
import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
@@ -77,18 +76,15 @@ export const transformGroupByDataToBarChartData = ({
});
const queryName = getGroupByQueryName(objectMetadataItem);
const queryResults = groupByData[queryName];
const rawResults = groupByData[queryName];
if (!isDefined(queryResults) || !Array.isArray(queryResults)) {
if (!isDefined(rawResults) || !Array.isArray(rawResults)) {
return {
...EMPTY_BAR_CHART_RESULT,
indexBy: indexByKey,
};
}
// TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600)
const rawResults = queryResults.slice(0, GRAPH_MAXIMUM_NUMBER_OF_GROUPS);
const showXAxis =
configuration.axisNameDisplay === AxisNameDisplay.X ||
configuration.axisNameDisplay === AxisNameDisplay.BOTH;
@@ -2,6 +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 { GRAPH_MAXIMUM_NUMBER_OF_GROUPS } from '@/page-layout/widgets/graph/constants/GraphMaximumNumberOfGroups.constant';
import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
@@ -41,13 +42,17 @@ export const transformOneDimensionalGroupByToBarChartData = ({
subFieldName: configuration.groupBySubFieldNameX,
});
const data: BarChartDataItem[] = rawResults.map((result) => {
// TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600)
const limitedResults = rawResults.slice(0, GRAPH_MAXIMUM_NUMBER_OF_GROUPS);
const data: BarChartDataItem[] = limitedResults.map((result) => {
const dimensionValues = result.groupByDimensionValues;
const xValue = isDefined(dimensionValues?.[0])
? formatDimensionValue({
value: dimensionValues[0],
fieldMetadata: groupByFieldX,
subFieldName: configuration.groupBySubFieldNameX ?? undefined,
})
: '';
@@ -1,6 +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 { GRAPH_MAXIMUM_NUMBER_OF_GROUPS } from '@/page-layout/widgets/graph/constants/GraphMaximumNumberOfGroups.constant';
import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
@@ -42,6 +43,7 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
});
const dataMap = new Map<string, BarChartDataItem>();
const xValues = new Set<string>();
const yValues = new Set<string>();
rawResults.forEach((result) => {
@@ -51,12 +53,28 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
const xValue = formatDimensionValue({
value: dimensionValues[0],
fieldMetadata: groupByFieldX,
subFieldName: configuration.groupBySubFieldNameX ?? undefined,
});
const yValue = formatDimensionValue({
value: dimensionValues[1],
fieldMetadata: groupByFieldY,
subFieldName: configuration.groupBySubFieldNameY ?? undefined,
});
// TODO: Add a limit to the query instead of checking here (issue: twentyhq/core-team-issues#1600)
const isNewX = !xValues.has(xValue);
const isNewY = !yValues.has(yValue);
const totalUniqueDimensions = xValues.size * yValues.size;
const additionalDimensions =
(isNewX ? 1 : 0) * yValues.size + (isNewY ? 1 : 0) * xValues.size;
if (
totalUniqueDimensions + additionalDimensions >
GRAPH_MAXIMUM_NUMBER_OF_GROUPS
) {
return;
}
const aggregateValue = computeAggregateValueFromGroupByResult({
rawResult: result,
aggregateField,
@@ -66,6 +84,9 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
objectMetadataItem,
});
if (!isDefined(aggregateValue)) return;
xValues.add(xValue);
yValues.add(yValue);
if (!dataMap.has(xValue)) {