feat(dashboard): extend number format option to bar, line and pie charts (#23505)
https://discord.com/channels/1130383047699738754/1509604545381142649 Extends the Format option (Short/Full) added for the Number widget in #21521 to bar, line and pie charts. Format controls the numbers printed on the chart face: data labels and the pie center metric. Axis ticks stay abbreviated and tooltips always show the full value. Defaults to Short, so existing charts render unchanged. Server: nullable `numberFormat` on the bar/line/pie configuration DTOs, exposed in the dashboard AI tool schema. No migration, configuration is jsonb. Deferred: - The Format row has no visible effect while data labels are off, since tooltips are always full. - Number widget format defaults differ by field type (CURRENCY defaults to Short, NUMBER to Full). Pre-existing, untouched here. https://github.com/user-attachments/assets/0778f08a-6681-4e7a-8716-fb3026d1e01f <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23505?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -1142,6 +1142,7 @@ type PieChartConfiguration {
|
||||
showCenterMetric: Boolean
|
||||
displayLegend: Boolean
|
||||
hideEmptyCategory: Boolean
|
||||
numberFormat: ChartNumberFormat
|
||||
splitMultiValueFields: Boolean
|
||||
description: String
|
||||
color: String
|
||||
@@ -1195,6 +1196,7 @@ type LineChartConfiguration {
|
||||
axisNameDisplay: AxisNameDisplay
|
||||
displayDataLabel: Boolean
|
||||
displayLegend: Boolean
|
||||
numberFormat: ChartNumberFormat
|
||||
rangeMin: Float
|
||||
rangeMax: Float
|
||||
description: String
|
||||
@@ -1238,6 +1240,7 @@ type BarChartConfiguration {
|
||||
axisNameDisplay: AxisNameDisplay
|
||||
displayDataLabel: Boolean
|
||||
displayLegend: Boolean
|
||||
numberFormat: ChartNumberFormat
|
||||
rangeMin: Float
|
||||
rangeMax: Float
|
||||
description: String
|
||||
|
||||
@@ -821,6 +821,7 @@ export interface PieChartConfiguration {
|
||||
showCenterMetric?: Scalars['Boolean']
|
||||
displayLegend?: Scalars['Boolean']
|
||||
hideEmptyCategory?: Scalars['Boolean']
|
||||
numberFormat?: ChartNumberFormat
|
||||
splitMultiValueFields?: Scalars['Boolean']
|
||||
description?: Scalars['String']
|
||||
color?: Scalars['String']
|
||||
@@ -857,6 +858,7 @@ export interface LineChartConfiguration {
|
||||
axisNameDisplay?: AxisNameDisplay
|
||||
displayDataLabel?: Scalars['Boolean']
|
||||
displayLegend?: Scalars['Boolean']
|
||||
numberFormat?: ChartNumberFormat
|
||||
rangeMin?: Scalars['Float']
|
||||
rangeMax?: Scalars['Float']
|
||||
description?: Scalars['String']
|
||||
@@ -898,6 +900,7 @@ export interface BarChartConfiguration {
|
||||
axisNameDisplay?: AxisNameDisplay
|
||||
displayDataLabel?: Scalars['Boolean']
|
||||
displayLegend?: Scalars['Boolean']
|
||||
numberFormat?: ChartNumberFormat
|
||||
rangeMin?: Scalars['Float']
|
||||
rangeMax?: Scalars['Float']
|
||||
description?: Scalars['String']
|
||||
@@ -3987,6 +3990,7 @@ export interface PieChartConfigurationGenqlSelection{
|
||||
showCenterMetric?: boolean | number
|
||||
displayLegend?: boolean | number
|
||||
hideEmptyCategory?: boolean | number
|
||||
numberFormat?: boolean | number
|
||||
splitMultiValueFields?: boolean | number
|
||||
description?: boolean | number
|
||||
color?: boolean | number
|
||||
@@ -4016,6 +4020,7 @@ export interface LineChartConfigurationGenqlSelection{
|
||||
axisNameDisplay?: boolean | number
|
||||
displayDataLabel?: boolean | number
|
||||
displayLegend?: boolean | number
|
||||
numberFormat?: boolean | number
|
||||
rangeMin?: boolean | number
|
||||
rangeMax?: boolean | number
|
||||
description?: boolean | number
|
||||
@@ -4055,6 +4060,7 @@ export interface BarChartConfigurationGenqlSelection{
|
||||
axisNameDisplay?: boolean | number
|
||||
displayDataLabel?: boolean | number
|
||||
displayLegend?: boolean | number
|
||||
numberFormat?: boolean | number
|
||||
rangeMin?: boolean | number
|
||||
rangeMax?: boolean | number
|
||||
description?: boolean | number
|
||||
|
||||
@@ -2230,6 +2230,9 @@ export default {
|
||||
"hideEmptyCategory": [
|
||||
3
|
||||
],
|
||||
"numberFormat": [
|
||||
91
|
||||
],
|
||||
"splitMultiValueFields": [
|
||||
3
|
||||
],
|
||||
@@ -2309,6 +2312,9 @@ export default {
|
||||
"displayLegend": [
|
||||
3
|
||||
],
|
||||
"numberFormat": [
|
||||
91
|
||||
],
|
||||
"rangeMin": [
|
||||
16
|
||||
],
|
||||
@@ -2407,6 +2413,9 @@ export default {
|
||||
"displayLegend": [
|
||||
3
|
||||
],
|
||||
"numberFormat": [
|
||||
91
|
||||
],
|
||||
"rangeMin": [
|
||||
16
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
+15
@@ -63,6 +63,21 @@ describe('transformAggregateRawValueIntoAggregateDisplayValue', () => {
|
||||
).toBe('153,909');
|
||||
});
|
||||
|
||||
it('should format large COUNT values with the SHORT chart number format', () => {
|
||||
expect(
|
||||
transformAggregateRawValueIntoAggregateDisplayValue({
|
||||
aggregateFieldMetadataItem: undefined,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateRawValue: 153909,
|
||||
dateFormat: DateFormat.DAY_FIRST,
|
||||
timeFormat: TimeFormat.HOUR_24,
|
||||
localeCatalog: enUS,
|
||||
timeZone: 'UTC',
|
||||
chartNumberFormat: ChartNumberFormat.SHORT,
|
||||
}),
|
||||
).toBe('153.9k');
|
||||
});
|
||||
|
||||
it('should format large COUNT values respecting the SPACES_AND_COMMA number format', () => {
|
||||
expect(
|
||||
transformAggregateRawValueIntoAggregateDisplayValue({
|
||||
|
||||
+5
-1
@@ -46,7 +46,11 @@ export const transformAggregateRawValueIntoAggregateDisplayValue = ({
|
||||
aggregateOperation as AggregateOperations,
|
||||
)
|
||||
) {
|
||||
return formatNumber(Number(aggregateRawValue), { format: numberFormat });
|
||||
const countValue = Number(aggregateRawValue);
|
||||
|
||||
return chartNumberFormat === ChartNumberFormat.SHORT
|
||||
? formatToShortNumber(countValue)
|
||||
: formatNumber(countValue, { format: numberFormat });
|
||||
} else if (!isDefined(aggregateFieldMetadataItem)) {
|
||||
return '-';
|
||||
} else if (
|
||||
|
||||
+3
@@ -54,6 +54,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
displayLegend
|
||||
numberFormat
|
||||
rangeMin
|
||||
rangeMax
|
||||
color
|
||||
@@ -84,6 +85,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
displayLegend
|
||||
numberFormat
|
||||
rangeMin
|
||||
rangeMax
|
||||
color
|
||||
@@ -107,6 +109,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
displayDataLabel
|
||||
showCenterMetric
|
||||
displayLegend
|
||||
numberFormat
|
||||
hideEmptyCategory
|
||||
splitMultiValueFields
|
||||
color
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { ChartNumberFormat } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CHART_NUMBER_FORMAT_DEFAULT = ChartNumberFormat.SHORT;
|
||||
+3
-1
@@ -32,6 +32,7 @@ type BarChartProps = {
|
||||
effectiveValueRange: { minimum: number; maximum: number };
|
||||
hasExplicitRangeBounds: boolean;
|
||||
formatOptions: GraphValueFormatOptions;
|
||||
axisFormatOptions?: GraphValueFormatOptions;
|
||||
axisConfig?: {
|
||||
xAxisLabel?: string;
|
||||
yAxisLabel?: string;
|
||||
@@ -69,6 +70,7 @@ export const BarChart = ({
|
||||
effectiveValueRange,
|
||||
hasExplicitRangeBounds,
|
||||
formatOptions,
|
||||
axisFormatOptions,
|
||||
axisConfig,
|
||||
rightTickLabels,
|
||||
dataLabelsConfig,
|
||||
@@ -107,7 +109,7 @@ export const BarChart = ({
|
||||
data,
|
||||
effectiveValueRange,
|
||||
hasExplicitRangeBounds,
|
||||
formatOptions,
|
||||
formatOptions: axisFormatOptions ?? formatOptions,
|
||||
groupMode,
|
||||
indexBy,
|
||||
keys,
|
||||
|
||||
+27
-2
@@ -1,3 +1,4 @@
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { pageLayoutDraggingWidgetIdComponentState } from '@/page-layout/states/pageLayoutDraggingWidgetIdComponentState';
|
||||
import { pageLayoutResizingWidgetIdComponentState } from '@/page-layout/states/pageLayoutResizingWidgetIdComponentState';
|
||||
import { GraphWidgetChartContainer } from '@/page-layout/widgets/graph/components/GraphWidgetChartContainer';
|
||||
@@ -34,6 +35,7 @@ import { useDebouncedCallback } from 'use-debounce';
|
||||
import { BarChartLayout } from '~/generated-metadata/graphql';
|
||||
|
||||
type GraphWidgetBarChartProps = {
|
||||
axisDisplayType?: GraphValueFormatOptions['displayType'];
|
||||
colorMode: GraphColorMode;
|
||||
data: BarChartDatum[];
|
||||
groupMode?: 'grouped' | 'stacked';
|
||||
@@ -50,6 +52,7 @@ type GraphWidgetBarChartProps = {
|
||||
showGrid?: boolean;
|
||||
showLegend?: boolean;
|
||||
showValues?: boolean;
|
||||
tooltipDisplayType?: GraphValueFormatOptions['displayType'];
|
||||
xAxisLabel?: string;
|
||||
yAxisLabel?: string;
|
||||
} & GraphValueFormatOptions;
|
||||
@@ -82,6 +85,8 @@ export const GraphWidgetBarChart = ({
|
||||
rangeMax,
|
||||
omitNullValues = false,
|
||||
displayType,
|
||||
axisDisplayType,
|
||||
tooltipDisplayType,
|
||||
decimals,
|
||||
prefix,
|
||||
suffix,
|
||||
@@ -124,15 +129,34 @@ export const GraphWidgetBarChart = ({
|
||||
|
||||
const allowDataTransitions = !isLayoutAnimating;
|
||||
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
const formatOptions = useMemo<GraphValueFormatOptions>(
|
||||
() => ({
|
||||
customFormatter,
|
||||
decimals,
|
||||
displayType,
|
||||
formatNumberFn: formatNumber,
|
||||
prefix,
|
||||
suffix,
|
||||
}),
|
||||
[customFormatter, decimals, displayType, prefix, suffix],
|
||||
[customFormatter, decimals, displayType, formatNumber, prefix, suffix],
|
||||
);
|
||||
|
||||
const axisFormatOptions = useMemo<GraphValueFormatOptions>(
|
||||
() => ({
|
||||
...formatOptions,
|
||||
displayType: axisDisplayType ?? displayType,
|
||||
}),
|
||||
[formatOptions, axisDisplayType, displayType],
|
||||
);
|
||||
|
||||
const tooltipFormatOptions = useMemo<GraphValueFormatOptions>(
|
||||
() => ({
|
||||
...formatOptions,
|
||||
displayType: tooltipDisplayType ?? displayType,
|
||||
}),
|
||||
[formatOptions, tooltipDisplayType, displayType],
|
||||
);
|
||||
|
||||
const { enrichedKeysMap, enrichedKeys, legendItems, visibleKeys } =
|
||||
@@ -262,6 +286,7 @@ export const GraphWidgetBarChart = ({
|
||||
hasExplicitRangeBounds={hasExplicitRangeBounds}
|
||||
enrichedKeysMap={enrichedKeysMap}
|
||||
formatOptions={formatOptions}
|
||||
axisFormatOptions={axisFormatOptions}
|
||||
rightTickLabels={rightTickLabels}
|
||||
groupMode={groupMode}
|
||||
hasNoData={hasNoData}
|
||||
@@ -280,7 +305,7 @@ export const GraphWidgetBarChart = ({
|
||||
containerRef={containerRef}
|
||||
dataByIndexValue={dataByIndexValue}
|
||||
enrichedKeys={enrichedKeys}
|
||||
formatOptions={formatOptions}
|
||||
formatOptions={tooltipFormatOptions}
|
||||
onMouseEnter={handleTooltipMouseEnter}
|
||||
onMouseLeave={handleTooltipMouseLeave}
|
||||
onSliceClick={onSliceClick}
|
||||
|
||||
+12
-1
@@ -7,6 +7,7 @@ import { type BarChartSlice } from '@/page-layout/widgets/graph/graph-widget-bar
|
||||
import { assertBarChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertBarChartWidget';
|
||||
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
|
||||
import { generateChartAggregateFilterKey } from '@/page-layout/widgets/graph/utils/generateChartAggregateFilterKey';
|
||||
import { getChartValueFormatOptions } from '@/page-layout/widgets/graph/utils/getChartValueFormatOptions';
|
||||
import { isFilteredViewRedirectionSupported } from '@/page-layout/widgets/graph/utils/isFilteredViewRedirectionSupported';
|
||||
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
|
||||
import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek';
|
||||
@@ -61,6 +62,13 @@ export const GraphWidgetBarChartRenderer = () => {
|
||||
const configuration = widget.configuration;
|
||||
const isPageLayoutInEditMode = useIsPageLayoutInEditMode();
|
||||
|
||||
const chartValueFormatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation: configuration.aggregateOperation,
|
||||
aggregateFieldMetadataId: configuration.aggregateFieldMetadataId,
|
||||
fieldMetadataItems: objectMetadataItem.fields,
|
||||
numberFormat: configuration.numberFormat,
|
||||
});
|
||||
|
||||
const axisNameDisplay = configuration.axisNameDisplay;
|
||||
|
||||
const showXLabel =
|
||||
@@ -143,7 +151,10 @@ export const GraphWidgetBarChartRenderer = () => {
|
||||
groupMode={groupMode}
|
||||
colorMode={colorMode}
|
||||
id={widget.id}
|
||||
displayType="shortNumber"
|
||||
decimals={chartValueFormatOptions.decimals}
|
||||
displayType={chartValueFormatOptions.displayType}
|
||||
axisDisplayType="shortNumber"
|
||||
tooltipDisplayType="number"
|
||||
rangeMin={configuration.rangeMin ?? undefined}
|
||||
rangeMax={configuration.rangeMax ?? undefined}
|
||||
omitNullValues={configuration.omitNullValues ?? false}
|
||||
|
||||
+20
-2
@@ -1,3 +1,4 @@
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { CHART_MOTION_CONFIG } from '@/page-layout/widgets/graph/constants/ChartMotionConfig';
|
||||
import { GraphWidgetChartContainer } from '@/page-layout/widgets/graph/components/GraphWidgetChartContainer';
|
||||
import { GraphWidgetLegend } from '@/page-layout/widgets/graph/components/GraphWidgetLegend';
|
||||
@@ -46,6 +47,7 @@ type LinesLayerProps = LineCustomSvgLayerProps<LineSeries>;
|
||||
type NoDataLayerWrapperProps = LineCustomSvgLayerProps<LineSeries>;
|
||||
|
||||
type GraphWidgetLineChartProps = {
|
||||
axisDisplayType?: GraphValueFormatOptions['displayType'];
|
||||
data: LineChartSeriesWithColor[];
|
||||
showLegend?: boolean;
|
||||
showGrid?: boolean;
|
||||
@@ -60,6 +62,7 @@ type GraphWidgetLineChartProps = {
|
||||
groupMode?: 'stacked';
|
||||
colorMode: GraphColorMode;
|
||||
onSliceClick?: (point: Point<LineSeries>) => void;
|
||||
tooltipDisplayType?: GraphValueFormatOptions['displayType'];
|
||||
} & GraphValueFormatOptions;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -84,6 +87,8 @@ export const GraphWidgetLineChart = ({
|
||||
rangeMax,
|
||||
omitNullValues = false,
|
||||
displayType,
|
||||
axisDisplayType,
|
||||
tooltipDisplayType,
|
||||
groupMode,
|
||||
colorMode,
|
||||
decimals,
|
||||
@@ -98,12 +103,25 @@ export const GraphWidgetLineChart = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [chartWidth, setChartWidth] = useState(0);
|
||||
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
const formatOptions: GraphValueFormatOptions = {
|
||||
displayType,
|
||||
decimals,
|
||||
prefix,
|
||||
suffix,
|
||||
customFormatter,
|
||||
formatNumberFn: formatNumber,
|
||||
};
|
||||
|
||||
const axisFormatOptions: GraphValueFormatOptions = {
|
||||
...formatOptions,
|
||||
displayType: axisDisplayType ?? displayType,
|
||||
};
|
||||
|
||||
const tooltipFormatOptions: GraphValueFormatOptions = {
|
||||
...formatOptions,
|
||||
displayType: tooltipDisplayType ?? displayType,
|
||||
};
|
||||
|
||||
const { enrichedSeries, nivoData, colors, legendItems, visibleData } =
|
||||
@@ -168,7 +186,7 @@ export const GraphWidgetLineChart = ({
|
||||
data,
|
||||
xAxisLabel,
|
||||
yAxisLabel,
|
||||
formatOptions,
|
||||
formatOptions: axisFormatOptions,
|
||||
effectiveMinimumValue,
|
||||
effectiveMaximumValue,
|
||||
});
|
||||
@@ -342,7 +360,7 @@ export const GraphWidgetLineChart = ({
|
||||
<GraphLineChartTooltip
|
||||
containerRef={containerRef}
|
||||
enrichedSeries={enrichedSeries}
|
||||
formatOptions={formatOptions}
|
||||
formatOptions={tooltipFormatOptions}
|
||||
isStacked={groupMode === 'stacked'}
|
||||
onSliceClick={onSliceClick}
|
||||
onMouseEnter={handleTooltipMouseEnter}
|
||||
|
||||
+12
-1
@@ -7,6 +7,7 @@ import { useGraphLineChartWidgetData } from '@/page-layout/widgets/graph/graph-w
|
||||
import { assertLineChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertLineChartWidget';
|
||||
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
|
||||
import { generateChartAggregateFilterKey } from '@/page-layout/widgets/graph/utils/generateChartAggregateFilterKey';
|
||||
import { getChartValueFormatOptions } from '@/page-layout/widgets/graph/utils/getChartValueFormatOptions';
|
||||
import { isFilteredViewRedirectionSupported } from '@/page-layout/widgets/graph/utils/isFilteredViewRedirectionSupported';
|
||||
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
|
||||
import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek';
|
||||
@@ -59,6 +60,13 @@ export const GraphWidgetLineChartRenderer = () => {
|
||||
const configuration = widget.configuration;
|
||||
const isPageLayoutInEditMode = useIsPageLayoutInEditMode();
|
||||
|
||||
const chartValueFormatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation: configuration.aggregateOperation,
|
||||
aggregateFieldMetadataId: configuration.aggregateFieldMetadataId,
|
||||
fieldMetadataItems: objectMetadataItem.fields,
|
||||
numberFormat: configuration.numberFormat,
|
||||
});
|
||||
|
||||
const hasGroupByOnSecondaryAxis = isDefined(
|
||||
configuration.secondaryAxisGroupByFieldMetadataId,
|
||||
);
|
||||
@@ -151,7 +159,10 @@ export const GraphWidgetLineChartRenderer = () => {
|
||||
omitNullValues={configuration.omitNullValues ?? false}
|
||||
groupMode={groupMode}
|
||||
colorMode={colorMode}
|
||||
displayType="shortNumber"
|
||||
decimals={chartValueFormatOptions.decimals}
|
||||
displayType={chartValueFormatOptions.displayType}
|
||||
axisDisplayType="shortNumber"
|
||||
tooltipDisplayType="number"
|
||||
onSliceClick={
|
||||
isPageLayoutInEditMode || !canRedirectToFilteredView
|
||||
? undefined
|
||||
|
||||
+13
-2
@@ -1,3 +1,4 @@
|
||||
import { useNumberFormat } from '@/localization/hooks/useNumberFormat';
|
||||
import { GraphWidgetChartContainer } from '@/page-layout/widgets/graph/components/GraphWidgetChartContainer';
|
||||
import { GraphWidgetLegend } from '@/page-layout/widgets/graph/components/GraphWidgetLegend';
|
||||
import { CHART_MOTION_CONFIG } from '@/page-layout/widgets/graph/constants/ChartMotionConfig';
|
||||
@@ -41,6 +42,7 @@ type GraphWidgetPieChartProps = {
|
||||
onSliceClick?: (datum: PieChartDataItemWithColor) => void;
|
||||
showDataLabels?: boolean;
|
||||
showCenterMetric?: boolean;
|
||||
tooltipDisplayType?: GraphValueFormatOptions['displayType'];
|
||||
} & GraphValueFormatOptions;
|
||||
|
||||
const emptyStateData: PieChartDataItemWithColor[] = [
|
||||
@@ -80,6 +82,7 @@ export const GraphWidgetPieChart = ({
|
||||
configuration,
|
||||
colorMode,
|
||||
displayType,
|
||||
tooltipDisplayType,
|
||||
decimals,
|
||||
prefix,
|
||||
suffix,
|
||||
@@ -95,12 +98,20 @@ export const GraphWidgetPieChart = ({
|
||||
graphWidgetPieTooltipComponentState,
|
||||
);
|
||||
|
||||
const { formatNumber } = useNumberFormat();
|
||||
|
||||
const formatOptions: GraphValueFormatOptions = {
|
||||
displayType,
|
||||
decimals,
|
||||
prefix,
|
||||
suffix,
|
||||
customFormatter,
|
||||
formatNumberFn: formatNumber,
|
||||
};
|
||||
|
||||
const tooltipFormatOptions: GraphValueFormatOptions = {
|
||||
...formatOptions,
|
||||
displayType: tooltipDisplayType ?? displayType,
|
||||
};
|
||||
|
||||
const { enrichedData, legendItems } = usePieChartData({
|
||||
@@ -229,8 +240,8 @@ export const GraphWidgetPieChart = ({
|
||||
<GraphPieChartTooltip
|
||||
containerRef={containerRef}
|
||||
enrichedData={enrichedData}
|
||||
formatOptions={formatOptions}
|
||||
displayType={displayType}
|
||||
formatOptions={tooltipFormatOptions}
|
||||
displayType={tooltipDisplayType ?? displayType}
|
||||
onSliceClick={onSliceClick}
|
||||
/>
|
||||
{showLegend && data.length > 0 && (
|
||||
|
||||
+11
-1
@@ -6,6 +6,7 @@ import { useGraphPieChartWidgetData } from '@/page-layout/widgets/graph/graph-wi
|
||||
import { type PieChartDataItemWithColor } from '@/page-layout/widgets/graph/graph-widget-pie-chart/types/PieChartDataItem';
|
||||
import { assertPieChartWidgetOrThrow } from '@/page-layout/widgets/graph/utils/assertPieChartWidget';
|
||||
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
|
||||
import { getChartValueFormatOptions } from '@/page-layout/widgets/graph/utils/getChartValueFormatOptions';
|
||||
import { isFilteredViewRedirectionSupported } from '@/page-layout/widgets/graph/utils/isFilteredViewRedirectionSupported';
|
||||
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
|
||||
import { useUserFirstDayOfTheWeek } from '@/ui/input/components/internal/date/hooks/useUserFirstDayOfTheWeek';
|
||||
@@ -58,6 +59,13 @@ export const GraphWidgetPieChartRenderer = () => {
|
||||
|
||||
const { userFirstDayOfTheWeek } = useUserFirstDayOfTheWeek();
|
||||
|
||||
const chartValueFormatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation: widget.configuration.aggregateOperation,
|
||||
aggregateFieldMetadataId: widget.configuration.aggregateFieldMetadataId,
|
||||
fieldMetadataItems: objectMetadataItem.fields,
|
||||
numberFormat: widget.configuration.numberFormat,
|
||||
});
|
||||
|
||||
const groupByField = objectMetadataItem.fields.find(
|
||||
(field) => field.id === widget.configuration.groupByFieldMetadataId,
|
||||
);
|
||||
@@ -109,7 +117,9 @@ export const GraphWidgetPieChartRenderer = () => {
|
||||
configuration={widget.configuration}
|
||||
showLegend={showLegend}
|
||||
colorMode={colorMode}
|
||||
displayType="shortNumber"
|
||||
decimals={chartValueFormatOptions.decimals}
|
||||
displayType={chartValueFormatOptions.displayType}
|
||||
tooltipDisplayType="number"
|
||||
onSliceClick={
|
||||
isPageLayoutInEditMode || !canRedirectToFilteredView
|
||||
? undefined
|
||||
|
||||
+21
-5
@@ -5,6 +5,7 @@ import { transformAggregateRawValueIntoAggregateDisplayValue } from '@/object-re
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
|
||||
import { CHART_NUMBER_FORMAT_DEFAULT } from '@/page-layout/widgets/graph/constants/ChartNumberFormatDefault';
|
||||
import { useGraphWidgetQueryCommon } from '@/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { UserContext } from '@/users/contexts/UserContext';
|
||||
@@ -82,7 +83,9 @@ export const usePieChartCenterMetricData = ({
|
||||
|
||||
const { dateFormat, timeFormat, timeZone } = useContext(UserContext);
|
||||
const dateLocale = useAtomStateValue(dateLocaleState);
|
||||
const { numberFormat, formatNumber } = useNumberFormat();
|
||||
const { numberFormat } = useNumberFormat();
|
||||
const chartNumberFormat =
|
||||
configuration.numberFormat ?? CHART_NUMBER_FORMAT_DEFAULT;
|
||||
|
||||
const aggregateFieldMetadataItem = objectMetadataItem.readableFields.find(
|
||||
findById(configuration.aggregateFieldMetadataId),
|
||||
@@ -122,9 +125,21 @@ export const usePieChartCenterMetricData = ({
|
||||
AggregateOperations.COUNT
|
||||
];
|
||||
|
||||
return isDefined(totalCountValue)
|
||||
? formatNumber(Number(totalCountValue))
|
||||
: totalCountValue;
|
||||
if (!isDefined(totalCountValue)) {
|
||||
return totalCountValue;
|
||||
}
|
||||
|
||||
return transformAggregateRawValueIntoAggregateDisplayValue({
|
||||
aggregateFieldMetadataItem,
|
||||
aggregateOperation: extendedAggregateOperation,
|
||||
aggregateRawValue: totalCountValue,
|
||||
dateFormat,
|
||||
localeCatalog: dateLocale.localeCatalog,
|
||||
timeFormat,
|
||||
timeZone,
|
||||
numberFormat,
|
||||
chartNumberFormat,
|
||||
});
|
||||
}
|
||||
|
||||
const aggregateRawValue =
|
||||
@@ -141,6 +156,7 @@ export const usePieChartCenterMetricData = ({
|
||||
timeFormat,
|
||||
timeZone,
|
||||
numberFormat,
|
||||
chartNumberFormat,
|
||||
});
|
||||
}, [
|
||||
aggregateFieldMetadataItem,
|
||||
@@ -152,7 +168,7 @@ export const usePieChartCenterMetricData = ({
|
||||
timeFormat,
|
||||
timeZone,
|
||||
numberFormat,
|
||||
formatNumber,
|
||||
chartNumberFormat,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { getChartValueDisplayType } from '@/page-layout/widgets/graph/utils/getChartValueDisplayType';
|
||||
import { ChartNumberFormat } from '~/generated-metadata/graphql';
|
||||
|
||||
describe('getChartValueDisplayType', () => {
|
||||
it('should return number when the format is FULL', () => {
|
||||
expect(getChartValueDisplayType(ChartNumberFormat.FULL)).toBe('number');
|
||||
});
|
||||
|
||||
it('should return shortNumber when the format is SHORT', () => {
|
||||
expect(getChartValueDisplayType(ChartNumberFormat.SHORT)).toBe(
|
||||
'shortNumber',
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to the default format when the format is unset', () => {
|
||||
expect(getChartValueDisplayType(null)).toBe('shortNumber');
|
||||
expect(getChartValueDisplayType(undefined)).toBe('shortNumber');
|
||||
});
|
||||
});
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getChartValueFormatOptions } from '@/page-layout/widgets/graph/utils/getChartValueFormatOptions';
|
||||
import { formatGraphValue } from '@/page-layout/widgets/graph/utils/graphFormatters';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
AggregateOperations,
|
||||
ChartNumberFormat,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
describe('getChartValueFormatOptions', () => {
|
||||
const aggregateFieldMetadataItem = {
|
||||
id: 'score-field-id',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
settings: {
|
||||
decimals: 2,
|
||||
type: 'number',
|
||||
},
|
||||
} as FieldMetadataItem;
|
||||
|
||||
const aggregateCurrencyFieldMetadataItem = {
|
||||
id: 'revenue-field-id',
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
} as FieldMetadataItem;
|
||||
|
||||
it.each([
|
||||
AggregateOperations.AVG,
|
||||
AggregateOperations.MAX,
|
||||
AggregateOperations.MIN,
|
||||
AggregateOperations.SUM,
|
||||
])(
|
||||
'should honor the aggregate number field decimal setting for %s',
|
||||
(aggregateOperation) => {
|
||||
const formatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadataId: aggregateFieldMetadataItem.id,
|
||||
fieldMetadataItems: [aggregateFieldMetadataItem],
|
||||
numberFormat: ChartNumberFormat.FULL,
|
||||
});
|
||||
|
||||
expect(formatGraphValue(123.4567, formatOptions)).toBe('123.46');
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
AggregateOperations.AVG,
|
||||
AggregateOperations.MAX,
|
||||
AggregateOperations.MIN,
|
||||
AggregateOperations.SUM,
|
||||
])(
|
||||
'should format aggregate currency fields with two decimals for %s',
|
||||
(aggregateOperation) => {
|
||||
const formatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadataId: aggregateCurrencyFieldMetadataItem.id,
|
||||
fieldMetadataItems: [aggregateCurrencyFieldMetadataItem],
|
||||
numberFormat: ChartNumberFormat.FULL,
|
||||
});
|
||||
|
||||
expect(formatGraphValue(123.4567, formatOptions)).toBe('123.46');
|
||||
},
|
||||
);
|
||||
|
||||
it('should keep the short format compact', () => {
|
||||
const formatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
aggregateFieldMetadataId: aggregateFieldMetadataItem.id,
|
||||
fieldMetadataItems: [aggregateFieldMetadataItem],
|
||||
numberFormat: ChartNumberFormat.SHORT,
|
||||
});
|
||||
|
||||
expect(formatGraphValue(123.4567, formatOptions)).toBe('123.5');
|
||||
});
|
||||
|
||||
it.each([
|
||||
AggregateOperations.COUNT,
|
||||
AggregateOperations.COUNT_EMPTY,
|
||||
AggregateOperations.COUNT_FALSE,
|
||||
AggregateOperations.COUNT_NOT_EMPTY,
|
||||
AggregateOperations.COUNT_TRUE,
|
||||
AggregateOperations.COUNT_UNIQUE_VALUES,
|
||||
AggregateOperations.PERCENTAGE_EMPTY,
|
||||
AggregateOperations.PERCENTAGE_NOT_EMPTY,
|
||||
])(
|
||||
'should ignore the aggregate number field decimal setting for %s',
|
||||
(aggregateOperation) => {
|
||||
const formatOptions = getChartValueFormatOptions({
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadataId: aggregateFieldMetadataItem.id,
|
||||
fieldMetadataItems: [aggregateFieldMetadataItem],
|
||||
numberFormat: ChartNumberFormat.FULL,
|
||||
});
|
||||
|
||||
expect(formatOptions.decimals).toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
+32
@@ -191,4 +191,36 @@ describe('formatGraphValue', () => {
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatNumberFn option', () => {
|
||||
const germanFormatNumberFn = (value: number) =>
|
||||
value.toLocaleString('de-DE');
|
||||
|
||||
it('should use the provided formatNumberFn for number display type', () => {
|
||||
const result = formatGraphValue(1234567, {
|
||||
displayType: 'number',
|
||||
formatNumberFn: germanFormatNumberFn,
|
||||
});
|
||||
|
||||
expect(result).toBe('1.234.567');
|
||||
});
|
||||
|
||||
it('should use the provided formatNumberFn for currency display type', () => {
|
||||
const result = formatGraphValue(1234567, {
|
||||
displayType: 'currency',
|
||||
formatNumberFn: germanFormatNumberFn,
|
||||
});
|
||||
|
||||
expect(result).toBe('$1.234.567');
|
||||
});
|
||||
|
||||
it('should not apply formatNumberFn to abbreviated short numbers', () => {
|
||||
const result = formatGraphValue(1234567, {
|
||||
displayType: 'shortNumber',
|
||||
formatNumberFn: germanFormatNumberFn,
|
||||
});
|
||||
|
||||
expect(result).toBe('1.2m');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { CHART_NUMBER_FORMAT_DEFAULT } from '@/page-layout/widgets/graph/constants/ChartNumberFormatDefault';
|
||||
import { type GraphValueFormatOptions } from '@/page-layout/widgets/graph/utils/graphFormatters';
|
||||
import { ChartNumberFormat } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getChartValueDisplayType = (
|
||||
numberFormat: ChartNumberFormat | null | undefined,
|
||||
): GraphValueFormatOptions['displayType'] =>
|
||||
(numberFormat ?? CHART_NUMBER_FORMAT_DEFAULT) === ChartNumberFormat.FULL
|
||||
? 'number'
|
||||
: 'shortNumber';
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions';
|
||||
import { getChartValueDisplayType } from '@/page-layout/widgets/graph/utils/getChartValueDisplayType';
|
||||
import { type GraphValueFormatOptions } from '@/page-layout/widgets/graph/utils/graphFormatters';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { findById, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type AggregateOperations as GeneratedAggregateOperations,
|
||||
type ChartNumberFormat,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type GetChartValueFormatOptionsParams = {
|
||||
aggregateOperation: GeneratedAggregateOperations;
|
||||
aggregateFieldMetadataId: string;
|
||||
fieldMetadataItems: FieldMetadataItem[];
|
||||
numberFormat: ChartNumberFormat | null | undefined;
|
||||
};
|
||||
|
||||
export const getChartValueFormatOptions = ({
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadataId,
|
||||
fieldMetadataItems,
|
||||
numberFormat,
|
||||
}: GetChartValueFormatOptionsParams): GraphValueFormatOptions => {
|
||||
const aggregateFieldMetadataItem = fieldMetadataItems.find(
|
||||
findById(aggregateFieldMetadataId),
|
||||
);
|
||||
|
||||
const aggregateFieldSettings =
|
||||
aggregateFieldMetadataItem?.type === FieldMetadataType.NUMBER
|
||||
? aggregateFieldMetadataItem.settings
|
||||
: undefined;
|
||||
|
||||
const aggregateNumberFieldDecimals =
|
||||
isDefined(aggregateFieldSettings) && 'decimals' in aggregateFieldSettings
|
||||
? aggregateFieldSettings.decimals
|
||||
: undefined;
|
||||
|
||||
const aggregateFieldDecimals =
|
||||
aggregateFieldMetadataItem?.type === FieldMetadataType.CURRENCY
|
||||
? 2
|
||||
: aggregateNumberFieldDecimals;
|
||||
|
||||
const shouldUseAggregateFieldDecimals =
|
||||
NON_STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(
|
||||
aggregateOperation as AggregateOperations,
|
||||
);
|
||||
|
||||
const decimals = shouldUseAggregateFieldDecimals
|
||||
? aggregateFieldDecimals
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
decimals,
|
||||
displayType: getChartValueDisplayType(numberFormat),
|
||||
};
|
||||
};
|
||||
+13
-4
@@ -1,3 +1,4 @@
|
||||
import { CHART_NUMBER_FORMAT_DEFAULT } from '@/page-layout/widgets/graph/constants/ChartNumberFormatDefault';
|
||||
import { usePageLayoutIdFromContextStore } from '@/side-panel/pages/page-layout/hooks/usePageLayoutIdFromContextStore';
|
||||
import { useUpdateCurrentWidgetConfig } from '@/side-panel/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
|
||||
import { useWidgetInEditMode } from '@/side-panel/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
@@ -20,13 +21,21 @@ export const ChartNumberFormatSelectionDropdownContent = () => {
|
||||
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
|
||||
if (
|
||||
!isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration')
|
||||
) {
|
||||
const isChartWithNumberFormat =
|
||||
isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration') ||
|
||||
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
|
||||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration') ||
|
||||
isWidgetConfigurationOfType(configuration, 'PieChartConfiguration');
|
||||
|
||||
if (!isChartWithNumberFormat) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentNumberFormat = configuration.numberFormat;
|
||||
const currentNumberFormat =
|
||||
configuration.numberFormat ??
|
||||
(configuration.__typename === 'AggregateChartConfiguration'
|
||||
? undefined
|
||||
: CHART_NUMBER_FORMAT_DEFAULT);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import { DATA_LABELS_SETTING } from '@/side-panel/pages/page-layout/constants/se
|
||||
import { DATE_GRANULARITY_X_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularityXSetting';
|
||||
import { DATE_GRANULARITY_Y_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularityYSetting';
|
||||
import { FILTER_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { FORMAT_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FormatSetting';
|
||||
import { GROUP_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/GroupBySetting';
|
||||
import { OMIT_NULL_VALUES_SETTING } from '@/side-panel/pages/page-layout/constants/settings/OmitNullValuesSetting';
|
||||
import { PRIMARY_SORT_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/PrimarySortBySetting';
|
||||
@@ -56,6 +57,7 @@ export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
AXIS_NAME_SETTING,
|
||||
STACKED_LINES_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
FORMAT_SETTING,
|
||||
SHOW_LEGEND_SETTING,
|
||||
],
|
||||
},
|
||||
|
||||
+2
@@ -7,6 +7,7 @@ import { DATA_LABELS_SETTING } from '@/side-panel/pages/page-layout/constants/se
|
||||
import { DATE_GRANULARITY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularitySetting';
|
||||
import { EACH_SLICE_REPRESENTS_SETTING } from '@/side-panel/pages/page-layout/constants/settings/EachSliceRepresentsSetting';
|
||||
import { FILTER_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { FORMAT_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FormatSetting';
|
||||
import { HIDE_EMPTY_CATEGORY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/HideEmptyCategorySetting';
|
||||
import { PRIMARY_SORT_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/PrimarySortBySetting';
|
||||
import { SHOW_LEGEND_SETTING } from '@/side-panel/pages/page-layout/constants/settings/ShowLegendSetting';
|
||||
@@ -32,6 +33,7 @@ export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
items: [
|
||||
COLORS_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
FORMAT_SETTING,
|
||||
SHOW_LEGEND_SETTING,
|
||||
CENTER_METRIC_SETTING,
|
||||
],
|
||||
|
||||
+29
@@ -11,6 +11,7 @@ import {
|
||||
AxisNameDisplay,
|
||||
BarChartGroupMode,
|
||||
BarChartLayout,
|
||||
ChartNumberFormat,
|
||||
FieldMetadataType,
|
||||
GraphOrderBy,
|
||||
RelationType,
|
||||
@@ -676,4 +677,32 @@ describe('useChartSettingsValues', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Number format setting', () => {
|
||||
it('should return the Full label for FORMAT when numberFormat is set on a bar chart', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
numberFormat: ChartNumberFormat.FULL,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.FORMAT,
|
||||
);
|
||||
|
||||
expect(value).toBe('Full');
|
||||
});
|
||||
|
||||
it('should return the default format label for FORMAT when numberFormat is not set', () => {
|
||||
const { result } = renderUseChartSettingsValues(
|
||||
buildBarChartConfiguration({}),
|
||||
);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.FORMAT,
|
||||
);
|
||||
|
||||
expect(value).toBe('Short');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+7
@@ -1,3 +1,4 @@
|
||||
import { CHART_NUMBER_FORMAT_DEFAULT } from '@/page-layout/widgets/graph/constants/ChartNumberFormatDefault';
|
||||
import { useGraphGroupBySortOptionLabels } from '@/side-panel/pages/page-layout/hooks/useGraphGroupBySortOptionLabels';
|
||||
import { useGraphXSortOptionLabels } from '@/side-panel/pages/page-layout/hooks/useGraphXSortOptionLabels';
|
||||
import { type ChartConfiguration } from '@/side-panel/pages/page-layout/types/ChartConfiguration';
|
||||
@@ -300,6 +301,12 @@ export const useChartSettingsValues = ({
|
||||
? (configuration.displayLegend ?? true)
|
||||
: true;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.FORMAT:
|
||||
if (isBarOrLineChart || isPieChart) {
|
||||
return getChartNumberFormatLabel(
|
||||
configuration.numberFormat ?? CHART_NUMBER_FORMAT_DEFAULT,
|
||||
);
|
||||
}
|
||||
|
||||
return configuration.__typename === 'AggregateChartConfiguration' &&
|
||||
isDefined(configuration.numberFormat)
|
||||
? getChartNumberFormatLabel(configuration.numberFormat)
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import { DATA_LABELS_SETTING } from '@/side-panel/pages/page-layout/constants/se
|
||||
import { DATE_GRANULARITY_X_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularityXSetting';
|
||||
import { DATE_GRANULARITY_Y_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularityYSetting';
|
||||
import { FILTER_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { FORMAT_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FormatSetting';
|
||||
import { GROUP_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/GroupBySetting';
|
||||
import { OMIT_NULL_VALUES_SETTING } from '@/side-panel/pages/page-layout/constants/settings/OmitNullValuesSetting';
|
||||
import { PRIMARY_SORT_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/PrimarySortBySetting';
|
||||
@@ -156,6 +157,7 @@ describe('getBarChartSettings', () => {
|
||||
AXIS_NAME_SETTING,
|
||||
STACKED_BARS_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
FORMAT_SETTING,
|
||||
SHOW_LEGEND_SETTING,
|
||||
]);
|
||||
});
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ import { DATA_LABELS_SETTING } from '@/side-panel/pages/page-layout/constants/se
|
||||
import { DATE_GRANULARITY_X_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularityXSetting';
|
||||
import { DATE_GRANULARITY_Y_SETTING } from '@/side-panel/pages/page-layout/constants/settings/DateGranularityYSetting';
|
||||
import { FILTER_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { FORMAT_SETTING } from '@/side-panel/pages/page-layout/constants/settings/FormatSetting';
|
||||
import { GROUP_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/GroupBySetting';
|
||||
import { OMIT_NULL_VALUES_SETTING } from '@/side-panel/pages/page-layout/constants/settings/OmitNullValuesSetting';
|
||||
import { PRIMARY_SORT_BY_SETTING } from '@/side-panel/pages/page-layout/constants/settings/PrimarySortBySetting';
|
||||
@@ -73,6 +74,7 @@ export const getBarChartSettings = (
|
||||
AXIS_NAME_SETTING,
|
||||
STACKED_BARS_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
FORMAT_SETTING,
|
||||
SHOW_LEGEND_SETTING,
|
||||
],
|
||||
},
|
||||
|
||||
+6
@@ -28,6 +28,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum';
|
||||
import { BarChartGroupMode } from 'src/engine/metadata-modules/page-layout-widget/enums/bar-chart-group-mode.enum';
|
||||
import { BarChartLayout } from 'src/engine/metadata-modules/page-layout-widget/enums/bar-chart-layout.enum';
|
||||
import { ChartNumberFormat } from 'src/engine/metadata-modules/page-layout-widget/enums/chart-number-format.enum';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout-widget/enums/date-granularity.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
@@ -135,6 +136,11 @@ export class BarChartConfigurationDTO implements BarChartConfiguration {
|
||||
@IsOptional()
|
||||
displayLegend?: boolean;
|
||||
|
||||
@Field(() => ChartNumberFormat, { nullable: true })
|
||||
@IsEnum(ChartNumberFormat)
|
||||
@IsOptional()
|
||||
numberFormat?: ChartNumberFormat;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
|
||||
+6
@@ -26,6 +26,7 @@ import {
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AxisNameDisplay } from 'src/engine/metadata-modules/page-layout-widget/enums/axis-name-display.enum';
|
||||
import { ChartNumberFormat } from 'src/engine/metadata-modules/page-layout-widget/enums/chart-number-format.enum';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout-widget/enums/date-granularity.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
@@ -136,6 +137,11 @@ export class LineChartConfigurationDTO implements LineChartConfiguration {
|
||||
@IsOptional()
|
||||
displayLegend?: boolean;
|
||||
|
||||
@Field(() => ChartNumberFormat, { nullable: true })
|
||||
@IsEnum(ChartNumberFormat)
|
||||
@IsOptional()
|
||||
numberFormat?: ChartNumberFormat;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
|
||||
+6
@@ -24,6 +24,7 @@ import {
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ChartNumberFormat } from 'src/engine/metadata-modules/page-layout-widget/enums/chart-number-format.enum';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'src/engine/metadata-modules/page-layout-widget/enums/date-granularity.enum';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
@@ -97,6 +98,11 @@ export class PieChartConfigurationDTO implements PieChartConfiguration {
|
||||
@IsOptional()
|
||||
hideEmptyCategory?: boolean;
|
||||
|
||||
@Field(() => ChartNumberFormat, { nullable: true })
|
||||
@IsEnum(ChartNumberFormat)
|
||||
@IsOptional()
|
||||
numberFormat?: ChartNumberFormat;
|
||||
|
||||
@Field(() => Boolean, { nullable: true, defaultValue: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
|
||||
@@ -165,6 +165,12 @@ export const chartFilterSchema = z
|
||||
|
||||
const displayDataLabelSchema = z.boolean().optional();
|
||||
const displayLegendSchema = z.boolean().optional();
|
||||
const chartNumberFormatSchema = z
|
||||
.enum(CHART_NUMBER_FORMAT_OPTIONS)
|
||||
.optional()
|
||||
.describe(
|
||||
'Display format for data label values: SHORT abbreviates large numbers (1.3m), FULL shows the complete number (1,300,090). Tooltips always show the full value.',
|
||||
);
|
||||
const showCenterMetricSchema = z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -391,6 +397,7 @@ const barChartConfigSchemaCore = z.object({
|
||||
.describe('Which axis labels to show'),
|
||||
displayDataLabel: displayDataLabelSchema,
|
||||
displayLegend: displayLegendSchema,
|
||||
numberFormat: chartNumberFormatSchema,
|
||||
groupMode: z
|
||||
.enum(BAR_CHART_GROUP_MODE_OPTIONS)
|
||||
.optional()
|
||||
@@ -482,6 +489,7 @@ const lineChartConfigSchemaCore = z.object({
|
||||
.describe('Which axis labels to show'),
|
||||
displayDataLabel: displayDataLabelSchema,
|
||||
displayLegend: displayLegendSchema,
|
||||
numberFormat: chartNumberFormatSchema,
|
||||
isStacked: z.boolean().optional().describe('Stack multiple lines'),
|
||||
isCumulative: z.boolean().optional().describe('Show running totals'),
|
||||
rangeMin: z.number().optional().describe('Y axis minimum value'),
|
||||
@@ -541,6 +549,7 @@ const pieChartConfigSchemaCore = z.object({
|
||||
color: z.enum(CHART_COLORS).optional().describe('Chart color theme'),
|
||||
displayDataLabel: displayDataLabelSchema,
|
||||
displayLegend: displayLegendSchema,
|
||||
numberFormat: chartNumberFormatSchema,
|
||||
showCenterMetric: showCenterMetricSchema,
|
||||
hideEmptyCategory: hideEmptyCategorySchema,
|
||||
filter: chartFilterSchema.optional(),
|
||||
|
||||
+8
@@ -82,6 +82,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "HORIZONTAL",
|
||||
"numberFormat": "SHORT",
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -130,6 +131,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "HORIZONTAL",
|
||||
"numberFormat": null,
|
||||
"omitNullValues": null,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -223,6 +225,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"firstDayOfTheWeek": 1,
|
||||
"isCumulative": null,
|
||||
"isStacked": null,
|
||||
"numberFormat": "SHORT",
|
||||
"omitNullValues": false,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -270,6 +273,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"firstDayOfTheWeek": 1,
|
||||
"isCumulative": null,
|
||||
"isStacked": null,
|
||||
"numberFormat": null,
|
||||
"omitNullValues": null,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -318,6 +322,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"numberFormat": "SHORT",
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
@@ -355,6 +360,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"numberFormat": null,
|
||||
"orderBy": "FIELD_ASC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
@@ -444,6 +450,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "VERTICAL",
|
||||
"numberFormat": "SHORT",
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -492,6 +499,7 @@ exports[`Page layout widget creation should succeed should create a page layout
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "VERTICAL",
|
||||
"numberFormat": null,
|
||||
"omitNullValues": null,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
|
||||
+4
@@ -49,6 +49,7 @@ exports[`Page layout widget update should succeed from a GRAPH widget should upd
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "HORIZONTAL",
|
||||
"numberFormat": null,
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -96,6 +97,7 @@ exports[`Page layout widget update should succeed from a GRAPH widget should upd
|
||||
"firstDayOfTheWeek": 1,
|
||||
"isCumulative": null,
|
||||
"isStacked": null,
|
||||
"numberFormat": null,
|
||||
"omitNullValues": false,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
@@ -144,6 +146,7 @@ exports[`Page layout widget update should succeed from a GRAPH widget should upd
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"numberFormat": null,
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
@@ -181,6 +184,7 @@ exports[`Page layout widget update should succeed from a GRAPH widget should upd
|
||||
"groupMode": null,
|
||||
"isCumulative": null,
|
||||
"layout": "VERTICAL",
|
||||
"numberFormat": null,
|
||||
"omitNullValues": true,
|
||||
"primaryAxisDateGranularity": "DAY",
|
||||
"primaryAxisGroupByFieldMetadataId": Any<String>,
|
||||
|
||||
+3
@@ -17,6 +17,7 @@ export const WIDGET_CONFIGURATION_GQL_FIELDS = `
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
displayLegend
|
||||
numberFormat
|
||||
rangeMin
|
||||
rangeMax
|
||||
color
|
||||
@@ -46,6 +47,7 @@ export const WIDGET_CONFIGURATION_GQL_FIELDS = `
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
displayLegend
|
||||
numberFormat
|
||||
rangeMin
|
||||
rangeMax
|
||||
color
|
||||
@@ -68,6 +70,7 @@ export const WIDGET_CONFIGURATION_GQL_FIELDS = `
|
||||
displayDataLabel
|
||||
showCenterMetric
|
||||
displayLegend
|
||||
numberFormat
|
||||
color
|
||||
description
|
||||
filter
|
||||
|
||||
+4
@@ -157,6 +157,7 @@ describe('Page layout widget creation should succeed', () => {
|
||||
primaryAxisGroupByFieldMetadataId: testSetup.fieldMetadataId2,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
displayDataLabel: true,
|
||||
numberFormat: ChartNumberFormat.SHORT,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
color: 'red',
|
||||
description: 'Monthly revenue breakdown',
|
||||
@@ -196,6 +197,7 @@ describe('Page layout widget creation should succeed', () => {
|
||||
primaryAxisGroupByFieldMetadataId: testSetup.fieldMetadataId2,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
displayDataLabel: true,
|
||||
numberFormat: ChartNumberFormat.SHORT,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
color: 'blue',
|
||||
description: 'Horizontal revenue breakdown',
|
||||
@@ -234,6 +236,7 @@ describe('Page layout widget creation should succeed', () => {
|
||||
orderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: true,
|
||||
displayLegend: true,
|
||||
numberFormat: ChartNumberFormat.SHORT,
|
||||
showCenterMetric: true,
|
||||
color: 'yellow',
|
||||
description: 'Distribution by category',
|
||||
@@ -269,6 +272,7 @@ describe('Page layout widget creation should succeed', () => {
|
||||
testSetup.fieldMetadataId3SubFieldName,
|
||||
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
|
||||
displayDataLabel: true,
|
||||
numberFormat: ChartNumberFormat.SHORT,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
color: 'cyan',
|
||||
description: 'Trend over time',
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ exports[`Page layout with tabs creation via update should succeed should create
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"numberFormat": null,
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
|
||||
+2
@@ -36,6 +36,7 @@ exports[`Page layout with tabs update should succeed should update page layout w
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"numberFormat": null,
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
@@ -133,6 +134,7 @@ exports[`Page layout with tabs update should succeed should update page layout w
|
||||
"groupByFieldMetadataId": Any<String>,
|
||||
"groupBySubFieldName": null,
|
||||
"manualSortOrder": null,
|
||||
"numberFormat": null,
|
||||
"orderBy": "VALUE_DESC",
|
||||
"showCenterMetric": true,
|
||||
"timezone": "UTC",
|
||||
|
||||
@@ -35,6 +35,7 @@ export type PieChartConfiguration = BaseChartConfiguration & {
|
||||
showCenterMetric?: boolean;
|
||||
displayLegend?: boolean;
|
||||
hideEmptyCategory?: boolean;
|
||||
numberFormat?: string;
|
||||
splitMultiValueFields?: boolean;
|
||||
};
|
||||
|
||||
@@ -54,6 +55,7 @@ export type BarChartConfiguration = BaseChartConfiguration & {
|
||||
splitMultiValueFields?: boolean;
|
||||
axisNameDisplay?: string;
|
||||
displayLegend?: boolean;
|
||||
numberFormat?: string;
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
groupMode?: string;
|
||||
@@ -77,6 +79,7 @@ export type LineChartConfiguration = BaseChartConfiguration & {
|
||||
splitMultiValueFields?: boolean;
|
||||
axisNameDisplay?: string;
|
||||
displayLegend?: boolean;
|
||||
numberFormat?: string;
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
isStacked?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user