[DASHBOARDS] Create display legend setting + animations (#16089)

## PR Description
- Create display legend setting
- Create pagination inside the legend
- Animate the legend on enter/exit
- Animate the transition between pages

## Video QA


https://github.com/user-attachments/assets/a68a4ea3-195f-4399-88f7-f83bc1a39cc4
This commit is contained in:
Raphaël Bosi
2025-11-26 17:03:23 +00:00
committed by GitHub
parent 7f441d741b
commit fc4dbb80de
34 changed files with 343 additions and 57 deletions
@@ -321,6 +321,7 @@ export type BarChartConfiguration = {
color?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
displayDataLabel?: Maybe<Scalars['Boolean']>;
displayLegend?: Maybe<Scalars['Boolean']>;
filter?: Maybe<Scalars['JSON']>;
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
graphType: GraphType;
@@ -1671,6 +1672,7 @@ export type LineChartConfiguration = {
color?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
displayDataLabel?: Maybe<Scalars['Boolean']>;
displayLegend?: Maybe<Scalars['Boolean']>;
filter?: Maybe<Scalars['JSON']>;
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
graphType: GraphType;
@@ -3105,6 +3107,7 @@ export type PieChartConfiguration = {
dateGranularity?: Maybe<ObjectRecordGroupByDateGranularity>;
description?: Maybe<Scalars['String']>;
displayDataLabel?: Maybe<Scalars['Boolean']>;
displayLegend?: Maybe<Scalars['Boolean']>;
filter?: Maybe<Scalars['JSON']>;
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
graphType: GraphType;
@@ -321,6 +321,7 @@ export type BarChartConfiguration = {
color?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
displayDataLabel?: Maybe<Scalars['Boolean']>;
displayLegend?: Maybe<Scalars['Boolean']>;
filter?: Maybe<Scalars['JSON']>;
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
graphType: GraphType;
@@ -1647,6 +1648,7 @@ export type LineChartConfiguration = {
color?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
displayDataLabel?: Maybe<Scalars['Boolean']>;
displayLegend?: Maybe<Scalars['Boolean']>;
filter?: Maybe<Scalars['JSON']>;
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
graphType: GraphType;
@@ -3039,6 +3041,7 @@ export type PieChartConfiguration = {
dateGranularity?: Maybe<ObjectRecordGroupByDateGranularity>;
description?: Maybe<Scalars['String']>;
displayDataLabel?: Maybe<Scalars['Boolean']>;
displayLegend?: Maybe<Scalars['Boolean']>;
filter?: Maybe<Scalars['JSON']>;
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
graphType: GraphType;
@@ -4751,7 +4754,7 @@ export type SearchQueryVariables = Exact<{
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
id: Scalars['String'];
@@ -4759,7 +4762,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
}>;
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
export type OnDbEventSubscriptionVariables = Exact<{
input: OnDbEventInput;
@@ -5085,6 +5088,7 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
omitNullValues
axisNameDisplay
displayDataLabel
displayLegend
rangeMin
rangeMax
color
@@ -5109,6 +5113,7 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
omitNullValues
axisNameDisplay
displayDataLabel
displayLegend
rangeMin
rangeMax
color
@@ -5127,6 +5132,7 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
dateGranularity
orderBy
displayDataLabel
displayLegend
color
description
filter
@@ -12,6 +12,7 @@ import { OMIT_NULL_VALUES_SETTING } from '@/command-menu/pages/page-layout/const
import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting';
import { RANGE_MAX_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMaxSetting';
import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMinSetting';
import { SHOW_LEGEND_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ShowLegendSetting';
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
import { STACKED_LINES_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedLineSettings';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
@@ -48,6 +49,7 @@ export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
AXIS_NAME_SETTING,
STACKED_LINES_SETTING,
DATA_LABELS_SETTING,
SHOW_LEGEND_SETTING,
],
},
];
@@ -6,6 +6,7 @@ import { DATE_GRANULARITY_SETTING } from '@/command-menu/pages/page-layout/const
import { EACH_SLICE_REPRESENTS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting';
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting';
import { SHOW_LEGEND_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ShowLegendSetting';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [
@@ -22,6 +23,6 @@ export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [
},
{
heading: 'Style',
items: [COLORS_SETTING, DATA_LABELS_SETTING],
items: [COLORS_SETTING, DATA_LABELS_SETTING, SHOW_LEGEND_SETTING],
},
];
@@ -22,4 +22,5 @@ export const CHART_CONFIGURATION_SETTING_LABELS = {
DATE_GRANULARITY_X: msg`Date granularity`,
DATE_GRANULARITY_Y: msg`Date granularity`,
DATE_GRANULARITY: msg`Date granularity`,
SHOW_LEGEND: msg`Legend`,
};
@@ -0,0 +1,12 @@
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
import { IconList } from 'twenty-ui/display';
export const SHOW_LEGEND_SETTING: ChartSettingsItem = {
isBoolean: true,
Icon: IconList,
label: CHART_CONFIGURATION_SETTING_LABELS.SHOW_LEGEND,
id: CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND,
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.GROUP_BY],
};
@@ -235,6 +235,8 @@ export const useChartSettingsValues = ({
return isPieChart && isDefined(configuration.dateGranularity)
? getDateGranularityLabel(configuration.dateGranularity)
: undefined;
case CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND:
return isPieChart ? (configuration.displayLegend ?? true) : true;
default:
return '';
}
@@ -21,6 +21,7 @@ export enum CHART_CONFIGURATION_SETTING_IDS {
DATE_GRANULARITY_X = 'DATE_GRANULARITY_X',
DATE_GRANULARITY_Y = 'DATE_GRANULARITY_Y',
DATE_GRANULARITY = 'DATE_GRANULARITY',
SHOW_LEGEND = 'SHOW_LEGEND',
}
export const CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP = {
@@ -35,4 +36,5 @@ export const CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP = {
[CHART_CONFIGURATION_SETTING_IDS.DATE_GRANULARITY_Y]:
'secondaryAxisGroupByDateGranularity',
[CHART_CONFIGURATION_SETTING_IDS.DATE_GRANULARITY]: 'dateGranularity',
[CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND]: 'displayLegend',
} as const;
@@ -12,6 +12,7 @@ import { OMIT_NULL_VALUES_SETTING } from '@/command-menu/pages/page-layout/const
import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting';
import { RANGE_MAX_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMaxSetting';
import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMinSetting';
import { SHOW_LEGEND_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ShowLegendSetting';
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
import { STACKED_BARS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedBarsSetting';
import { IconAxisX, IconAxisY } from 'twenty-ui/display';
@@ -143,6 +144,7 @@ describe('getBarChartSettings', () => {
AXIS_NAME_SETTING,
STACKED_BARS_SETTING,
DATA_LABELS_SETTING,
SHOW_LEGEND_SETTING,
]);
});
});
@@ -12,6 +12,7 @@ import { OMIT_NULL_VALUES_SETTING } from '@/command-menu/pages/page-layout/const
import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting';
import { RANGE_MAX_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMaxSetting';
import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMinSetting';
import { SHOW_LEGEND_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ShowLegendSetting';
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
import { STACKED_BARS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedBarsSetting';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
@@ -65,6 +66,7 @@ export const getBarChartSettings = (
AXIS_NAME_SETTING,
STACKED_BARS_SETTING,
DATA_LABELS_SETTING,
SHOW_LEGEND_SETTING,
],
},
];
@@ -74,6 +74,12 @@ export const shouldHideChartSetting = (
);
}
}
if (item.id === CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND) {
if (configuration.__typename === 'PieChartConfiguration') {
return false;
}
}
}
return (
@@ -31,6 +31,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
omitNullValues
axisNameDisplay
displayDataLabel
displayLegend
rangeMin
rangeMax
color
@@ -55,6 +56,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
omitNullValues
axisNameDisplay
displayDataLabel
displayLegend
rangeMin
rangeMax
color
@@ -73,6 +75,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
dateGranularity
orderBy
displayDataLabel
displayLegend
color
description
filter
@@ -1,5 +1,18 @@
import { CHART_LEGEND_ITEM_THRESHOLD } from '@/page-layout/widgets/graph/constants/ChartLegendItemThreshold';
import { GraphWidgetLegendDot } from '@/page-layout/widgets/graph/components/GraphWidgetLegendDot';
import { LEGEND_ITEM_ESTIMATED_WIDTH } from '@/page-layout/widgets/graph/constants/LegendItemEstimatedWidth.constant';
import { LEGEND_LABEL_MAX_WIDTH } from '@/page-layout/widgets/graph/constants/LegendLabelMaxWidth.constant';
import { LEGEND_PAGINATION_CONTROLS_WIDTH } from '@/page-layout/widgets/graph/constants/LegendPaginationControlsWidth.constant';
import { NodeDimensionEffect } from '@/ui/utilities/dimensions/components/NodeDimensionEffect';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimatePresence, motion } from 'framer-motion';
import { useRef, useState } from 'react';
import {
IconChevronLeft,
IconChevronRight,
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
export type GraphWidgetLegendItem = {
id: string;
@@ -12,59 +25,232 @@ type GraphWidgetLegendProps = {
show?: boolean;
};
const StyledLegendContainer = styled.div`
display: flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.spacing(3)};
justify-content: center;
padding-top: ${({ theme }) => theme.spacing(3)};
type AnimationDirection = 'forward' | 'backward';
const StyledAnimationClipContainer = styled.div`
flex: 1;
min-width: 0;
overflow: hidden;
position: relative;
`;
const StyledLegendItem = styled.div`
const StyledLegendMotionWrapper = styled(motion.div)`
width: 100%;
`;
const StyledItemsWrapper = styled(motion.div)<{ centered?: boolean }>`
display: flex;
gap: ${({ theme }) => theme.spacing(3)};
flex-wrap: nowrap;
flex: 1;
min-width: 0;
justify-content: ${({ centered }) => (centered ? 'center' : 'flex-start')};
`;
const StyledLegendContainer = styled.div<{ needsPagination: boolean }>`
display: flex;
flex-wrap: nowrap;
gap: ${({ theme }) => theme.spacing(3)};
justify-content: ${({ needsPagination }) =>
needsPagination ? 'flex-start' : 'center'};
padding-top: ${({ theme }) => theme.spacing(3)};
overflow: hidden;
width: 100%;
align-items: center;
`;
const StyledLegendItem = styled.div<{ canShrink?: boolean }>`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
flex-shrink: ${({ canShrink }) => (canShrink ? 1 : 0)};
min-width: 0;
`;
const StyledLegendLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
const StyledLegendLabel = styled.div<{ fixedWidth?: boolean }>`
color: ${({ theme }) => theme.font.color.secondary};
${({ fixedWidth }) => fixedWidth && `width: ${LEGEND_LABEL_MAX_WIDTH}px;`}
overflow: hidden;
`;
const StyledDot = styled.div<{ color: string }>`
background: ${({ color }) => color};
border-radius: 50%;
height: 6px;
width: 6px;
const StyledPaginationContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
flex-shrink: 0;
gap: ${({ theme }) => theme.spacing(0.5)};
`;
const StyledPaginationIndicator = styled.span`
color: ${({ theme }) => theme.font.color.light};
font-size: ${({ theme }) => theme.font.size.xs};
`;
const legendEnterExitVariants = {
hidden: {
y: 10,
opacity: 0,
},
visible: {
y: 0,
opacity: 1,
},
exit: {
y: 10,
opacity: 0,
},
};
export const GraphWidgetLegend = ({
items,
show = true,
}: GraphWidgetLegendProps) => {
const areThereTooManyItems = items.length > CHART_LEGEND_ITEM_THRESHOLD;
const containerRef = useRef<HTMLDivElement>(null);
const isItASingleItem = items.length === 1;
const [currentPage, setCurrentPage] = useState(0);
const thereAreNoItems = items.length === 0;
const [containerWidth, setContainerWidth] = useState(0);
const shouldShowLegend =
show && !areThereTooManyItems && !isItASingleItem && !thereAreNoItems;
const [animationDirection, setAnimationDirection] =
useState<AnimationDirection>('forward');
if (!shouldShowLegend) {
return null;
}
const theme = useTheme();
const shouldShowLegend = show && items.length > 1;
const availableWidth = containerWidth - LEGEND_PAGINATION_CONTROLS_WIDTH;
const itemsPerPage = Math.max(
1,
Math.floor(availableWidth / LEGEND_ITEM_ESTIMATED_WIDTH),
);
const needsPagination = items.length > itemsPerPage;
const totalPages = Math.ceil(items.length / itemsPerPage);
const safeCurrentPage = Math.min(currentPage, Math.max(0, totalPages - 1));
const startIndex = safeCurrentPage * itemsPerPage;
const visibleItems = needsPagination
? items.slice(startIndex, startIndex + itemsPerPage)
: items;
const handlePreviousPage = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setAnimationDirection('backward');
setCurrentPage((prevPage) => Math.max(0, prevPage - 1));
};
const handleNextPage = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
setAnimationDirection('forward');
setCurrentPage((prevPage) => Math.min(totalPages - 1, prevPage + 1));
};
const slideOffset = availableWidth;
const variants = {
enter: (direction: AnimationDirection) => ({
x: direction === 'forward' ? slideOffset : -slideOffset,
}),
center: {
x: 0,
},
exit: (direction: AnimationDirection) => ({
x: direction === 'forward' ? -slideOffset : slideOffset,
}),
};
return (
<StyledLegendContainer>
{items.map((item) => (
<StyledLegendItem key={item.id}>
<StyledDot color={item.color} />
<StyledLegendLabel>{item.label}</StyledLegendLabel>
</StyledLegendItem>
))}
</StyledLegendContainer>
<AnimatePresence mode="popLayout" initial={false}>
{shouldShowLegend && (
<StyledLegendMotionWrapper
variants={legendEnterExitVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={{
duration: theme.animation.duration.normal,
ease: 'easeInOut',
}}
>
<StyledLegendContainer
ref={containerRef}
needsPagination={needsPagination}
>
<NodeDimensionEffect
elementRef={containerRef}
onDimensionChange={({ width }) => {
const newItemsPerPage = Math.max(
1,
Math.floor(
(width - LEGEND_PAGINATION_CONTROLS_WIDTH) /
LEGEND_ITEM_ESTIMATED_WIDTH,
),
);
if (newItemsPerPage !== itemsPerPage) {
setCurrentPage(0);
}
setContainerWidth(width);
}}
/>
{needsPagination && (
<StyledPaginationContainer>
<LightIconButton
onClick={handlePreviousPage}
disabled={safeCurrentPage === 0}
Icon={IconChevronLeft}
accent="tertiary"
/>
<StyledPaginationIndicator>
{safeCurrentPage + 1}/{totalPages}
</StyledPaginationIndicator>
<LightIconButton
onClick={handleNextPage}
disabled={safeCurrentPage === totalPages - 1}
Icon={IconChevronRight}
/>
</StyledPaginationContainer>
)}
<StyledAnimationClipContainer>
<AnimatePresence
mode="popLayout"
custom={animationDirection}
initial={false}
>
<StyledItemsWrapper
key={safeCurrentPage}
custom={animationDirection}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
duration: theme.animation.duration.normal,
ease: 'easeInOut',
}}
centered={!needsPagination}
>
{visibleItems.map((item) => (
<StyledLegendItem
key={item.id}
canShrink={!needsPagination}
>
<GraphWidgetLegendDot color={item.color} />
<StyledLegendLabel fixedWidth={needsPagination}>
<OverflowingTextWithTooltip text={item.label} />
</StyledLegendLabel>
</StyledLegendItem>
))}
</StyledItemsWrapper>
</AnimatePresence>
</StyledAnimationClipContainer>
</StyledLegendContainer>
</StyledLegendMotionWrapper>
)}
</AnimatePresence>
);
};
@@ -0,0 +1,17 @@
import styled from '@emotion/styled';
type GraphWidgetLegendDotProps = {
color: string;
};
const StyledDot = styled.div<{ color: string }>`
background: ${({ color }) => color};
border-radius: 2px;
height: 8px;
width: 8px;
flex-shrink: 0;
`;
export const GraphWidgetLegendDot = ({ color }: GraphWidgetLegendDotProps) => {
return <StyledDot color={color} />;
};
@@ -1,12 +1,13 @@
import { GRAPH_TOOLTIP_MAX_WIDTH_PX } from '@/page-layout/widgets/graph/components/constants/GraphTooltipMaxWidthPx';
import { GRAPH_TOOLTIP_MIN_WIDTH_PX } from '@/page-layout/widgets/graph/components/constants/GraphTooltipMinWidthPx';
import { GRAPH_TOOLTIP_SCROLL_MAX_HEIGHT_PX } from '@/page-layout/widgets/graph/components/constants/GraphTooltipScrollMaxHeightPx';
import { GraphWidgetLegendDot } from '@/page-layout/widgets/graph/components/GraphWidgetLegendDot';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { IconArrowUpRight } from 'twenty-ui/display';
import { isDefined } from 'twenty-shared/utils';
import { IconArrowUpRight } from 'twenty-ui/display';
const StyledTooltip = styled.div`
background: ${({ theme }) => theme.background.primary};
@@ -43,14 +44,6 @@ const StyledTooltipRowContainer = styled.div`
overflow-y: auto;
`;
const StyledDot = styled.div<{ color: string }>`
background: ${({ color }) => color};
border-radius: 50%;
height: 6px;
width: 6px;
flex-shrink: 0;
`;
const StyledTooltipLink = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.light};
@@ -168,7 +161,7 @@ export const GraphWidgetTooltip = ({
shouldHighlight && highlightedKey === item.key;
return (
<StyledTooltipRow key={item.key}>
<StyledDot color={item.dotColor} />
<GraphWidgetLegendDot color={item.dotColor} />
<StyledTooltipRowRightContent>
<StyledTooltipLabel isHighlighted={isHighlighted}>
{item.label}
@@ -1 +0,0 @@
export const CHART_LEGEND_ITEM_THRESHOLD = 10;
@@ -0,0 +1,2 @@
// Estimated width of a legend item in pixels (dot + gap + label + container gap)
export const LEGEND_ITEM_ESTIMATED_WIDTH = 100;
@@ -0,0 +1,2 @@
// Max width of a legend label before truncation
export const LEGEND_LABEL_MAX_WIDTH = 80;
@@ -0,0 +1,2 @@
// Width of pagination controls (prev button + indicator + next button)
export const LEGEND_PAGINATION_CONTROLS_WIDTH = 70;
@@ -1,3 +1,4 @@
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
import { GraphWidgetChartHasTooManyGroupsEffect } from '@/page-layout/widgets/graph/components/GraphWidgetChartHasTooManyGroupsEffect';
import { useGraphBarChartWidgetData } from '@/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData';
@@ -5,7 +6,6 @@ import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBa
import { getEffectiveGroupMode } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getEffectiveGroupMode';
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
import { generateChartAggregateFilterKey } from '@/page-layout/widgets/graph/utils/generateChartAggregateFilterKey';
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector';
import { type ComputedDatum } from '@nivo/bar';
@@ -40,6 +40,7 @@ export const GraphWidgetBarChartRenderer = ({
xAxisLabel,
yAxisLabel,
showDataLabels,
showLegend,
layout,
loading,
hasTooManyGroups,
@@ -116,6 +117,7 @@ export const GraphWidgetBarChartRenderer = ({
xAxisLabel={xAxisLabel}
yAxisLabel={yAxisLabel}
showValues={showDataLabels}
showLegend={showLegend}
layout={layout}
groupMode={groupMode}
id={widget.id}
@@ -17,9 +17,9 @@ export const useBarChartTheme = () => {
},
legend: {
text: {
fill: theme.font.color.secondary,
fontSize: 12,
fontWeight: theme.font.weight.regular,
fill: theme.font.color.primary,
fontSize: theme.font.size.sm,
fontWeight: theme.font.weight.medium,
},
},
},
@@ -22,6 +22,7 @@ type UseGraphBarChartWidgetDataResult = {
xAxisLabel?: string;
yAxisLabel?: string;
showDataLabels: boolean;
showLegend: boolean;
layout?: BarChartLayout;
loading: boolean;
error?: Error;
@@ -37,6 +37,7 @@ type TransformGroupByDataToBarChartDataResult = {
xAxisLabel?: string;
yAxisLabel?: string;
showDataLabels: boolean;
showLegend: boolean;
layout?: BarChartLayout;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
@@ -50,6 +51,7 @@ const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = {
xAxisLabel: undefined,
yAxisLabel: undefined,
showDataLabels: false,
showLegend: true,
layout: BarChartLayout.VERTICAL,
hasTooManyGroups: false,
formattedToRawLookup: new Map(),
@@ -146,6 +148,7 @@ export const transformGroupByDataToBarChartData = ({
: undefined;
const showDataLabels = configuration.displayDataLabel ?? false;
const showLegend = configuration.displayLegend ?? true;
const isDateField =
groupByFieldX.type === FieldMetadataType.DATE ||
@@ -199,6 +202,7 @@ export const transformGroupByDataToBarChartData = ({
xAxisLabel,
yAxisLabel,
showDataLabels,
showLegend,
layout,
hasTooManyGroups: baseResult.hasTooManyGroups || dateRangeWasTruncated,
formattedToRawLookup: baseResult.formattedToRawLookup,
@@ -1,3 +1,4 @@
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
import { GraphWidgetChartHasTooManyGroupsEffect } from '@/page-layout/widgets/graph/components/GraphWidgetChartHasTooManyGroupsEffect';
import { LINE_CHART_IS_STACKED_DEFAULT } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartIsStackedDefault';
@@ -5,7 +6,6 @@ import { useGraphLineChartWidgetData } from '@/page-layout/widgets/graph/graphWi
import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint';
import { buildChartDrilldownQueryParams } from '@/page-layout/widgets/graph/utils/buildChartDrilldownQueryParams';
import { generateChartAggregateFilterKey } from '@/page-layout/widgets/graph/utils/generateChartAggregateFilterKey';
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { coreIndexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreIndexViewIdFromObjectMetadataItemFamilySelector';
import { type LineSeries, type Point } from '@nivo/line';
@@ -37,6 +37,7 @@ export const GraphWidgetLineChartRenderer = ({
xAxisLabel,
yAxisLabel,
showDataLabels,
showLegend,
hasTooManyGroups,
loading,
formattedToRawLookup,
@@ -113,6 +114,7 @@ export const GraphWidgetLineChartRenderer = ({
xAxisLabel={xAxisLabel}
yAxisLabel={yAxisLabel}
enablePointLabel={showDataLabels}
showLegend={showLegend}
rangeMin={configuration.rangeMin ?? undefined}
rangeMax={configuration.rangeMax ?? undefined}
omitNullValues={configuration.omitNullValues ?? false}
@@ -17,6 +17,7 @@ type UseGraphLineChartWidgetDataResult = {
xAxisLabel?: string;
yAxisLabel?: string;
showDataLabels: boolean;
showLegend: boolean;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
loading: boolean;
@@ -23,9 +23,9 @@ export const useLineChartTheme = () => {
},
legend: {
text: {
fill: theme.font.color.secondary,
fontSize: 12,
fontWeight: theme.font.weight.regular,
fill: theme.font.color.primary,
fontSize: theme.font.size.sm,
fontWeight: theme.font.weight.medium,
},
},
},
@@ -152,6 +152,14 @@ export const GraphWidgetPieChart = ({
arcLinkLabelsStraightLength={10}
arcLinkLabelsTextColor={theme.font.color.light}
arcLinkLabelsColor={theme.font.color.extraLight}
theme={{
labels: {
text: {
fontSize: theme.font.size.sm,
fontWeight: theme.font.weight.medium,
},
},
}}
/>
</StyledPieChartWrapper>
</GraphWidgetChartContainer>
@@ -32,8 +32,9 @@ export const GraphWidgetPieChartRenderer = ({
loading,
hasTooManyGroups,
objectMetadataItem,
formattedToRawLookup,
showLegend,
showDataLabels,
formattedToRawLookup,
} = useGraphPieChartWidgetData({
objectMetadataItemId: widget.objectMetadataId,
configuration: widget.configuration as PieChartConfiguration,
@@ -84,6 +85,7 @@ export const GraphWidgetPieChartRenderer = ({
<GraphWidgetPieChart
data={data}
id={widget.id}
showLegend={showLegend}
displayType="shortNumber"
onSliceClick={handleSliceClick}
showDataLabels={showDataLabels}
@@ -1,5 +1,6 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant';
import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant';
import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem';
import { transformGroupByDataToPieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData';
@@ -15,6 +16,7 @@ type UseGraphPieChartWidgetDataProps = {
type UseGraphPieChartWidgetDataResult = {
data: PieChartDataItem[];
showLegend: boolean;
loading: boolean;
error?: Error;
hasTooManyGroups: boolean;
@@ -23,9 +25,6 @@ type UseGraphPieChartWidgetDataResult = {
showDataLabels: boolean;
};
// TODO: Remove this once backend returns total group count
const EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS = 1;
export const useGraphPieChartWidgetData = ({
objectMetadataItemId,
configuration,
@@ -22,12 +22,14 @@ type TransformGroupByDataToPieChartDataParams = {
type TransformGroupByDataToPieChartDataResult = {
data: PieChartDataItem[];
showLegend: boolean;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
};
const EMPTY_PIE_CHART_RESULT: TransformGroupByDataToPieChartDataResult = {
data: [],
showLegend: true,
hasTooManyGroups: false,
formattedToRawLookup: new Map(),
};
@@ -99,8 +101,11 @@ export const transformGroupByDataToPieChartData = ({
};
});
const showLegend = configuration.displayLegend ?? true;
return {
data,
showLegend,
hasTooManyGroups: rawResults.length > PIE_CHART_MAXIMUM_NUMBER_OF_SLICES,
formattedToRawLookup,
};
@@ -27,6 +27,7 @@ type TransformGroupByDataToLineChartDataResult = {
xAxisLabel?: string;
yAxisLabel?: string;
showDataLabels: boolean;
showLegend: boolean;
hasTooManyGroups: boolean;
formattedToRawLookup: Map<string, RawDimensionValue>;
};
@@ -36,6 +37,7 @@ const EMPTY_LINE_CHART_RESULT: TransformGroupByDataToLineChartDataResult = {
xAxisLabel: undefined,
yAxisLabel: undefined,
showDataLabels: false,
showLegend: true,
hasTooManyGroups: false,
formattedToRawLookup: new Map(),
};
@@ -113,6 +115,7 @@ export const transformGroupByDataToLineChartData = ({
: undefined;
const showDataLabels = configuration.displayDataLabel ?? false;
const showLegend = configuration.displayLegend ?? true;
const baseResult = isDefined(groupByFieldY)
? transformTwoDimensionalGroupByToLineChartData({
@@ -140,6 +143,7 @@ export const transformGroupByDataToLineChartData = ({
xAxisLabel,
yAxisLabel,
showDataLabels,
showLegend,
formattedToRawLookup: baseResult.formattedToRawLookup,
};
};
@@ -108,6 +108,11 @@ export class BarChartConfigurationDTO {
@IsOptional()
displayDataLabel?: boolean;
@Field(() => Boolean, { nullable: true, defaultValue: true })
@IsBoolean()
@IsOptional()
displayLegend?: boolean;
@Field(() => Number, { nullable: true })
@IsNumber()
@IsOptional()
@@ -109,6 +109,11 @@ export class LineChartConfigurationDTO {
@IsOptional()
displayDataLabel?: boolean;
@Field(() => Boolean, { nullable: true, defaultValue: true })
@IsBoolean()
@IsOptional()
displayLegend?: boolean;
@Field(() => Number, { nullable: true })
@IsNumber()
@IsOptional()
@@ -71,6 +71,11 @@ export class PieChartConfigurationDTO {
@IsOptional()
displayDataLabel?: boolean;
@Field(() => Boolean, { nullable: true, defaultValue: true })
@IsBoolean()
@IsOptional()
displayLegend?: boolean;
@Field(() => String, { nullable: true })
@IsString()
@IsOptional()