diff --git a/web/src/api/asset_interfaces/metric/charts/chartConfigProps.ts b/web/src/api/asset_interfaces/metric/charts/chartConfigProps.ts index b6ae7a3e3..2a309c02b 100644 --- a/web/src/api/asset_interfaces/metric/charts/chartConfigProps.ts +++ b/web/src/api/asset_interfaces/metric/charts/chartConfigProps.ts @@ -6,7 +6,7 @@ import type { Y2AxisConfig, YAxisConfig } from './tickInterfaces'; -import type { ShowLegendHeadline, BarSortBy } from './etcInterfaces'; +import type { ShowLegendHeadline, BarSortBy, PieSortBy } from './etcInterfaces'; import type { GoalLine, Trendline } from './annotationInterfaces'; import type { ColumnSettings } from './columnInterfaces'; import type { IColumnLabelFormat } from './columnLabelInterfaces'; @@ -54,6 +54,7 @@ type ScatterChartProps = { }; type PieChartProps = { + pieSortBy?: PieSortBy; //OPTIONAL: default: value pieChartAxis: PieChartAxis; // Required for Pie pieDisplayLabelAs?: 'percent' | 'number'; //OPTIONAL: default: number pieShowInnerLabel?: boolean; //OPTIONAL: default true if donut width is set. If the data contains a percentage, set this as false. diff --git a/web/src/api/asset_interfaces/metric/charts/etcInterfaces.ts b/web/src/api/asset_interfaces/metric/charts/etcInterfaces.ts index 65444c5ea..e46bee118 100644 --- a/web/src/api/asset_interfaces/metric/charts/etcInterfaces.ts +++ b/web/src/api/asset_interfaces/metric/charts/etcInterfaces.ts @@ -1,5 +1,7 @@ export type BarSortBy = ('asc' | 'desc' | 'none')[]; //OPTIONAL: default is no sorting (none). The first item in the array will be the primary sort. The second item will be the secondary sort. This will only apply if the X axis type is not a date. +export type PieSortBy = 'value' | 'key' | null; //OPTIONAL: default: value + //current is used for line charts with export type ShowLegendHeadline = | false diff --git a/web/src/api/asset_interfaces/metric/defaults.ts b/web/src/api/asset_interfaces/metric/defaults.ts index 64437074a..bbd44f976 100644 --- a/web/src/api/asset_interfaces/metric/defaults.ts +++ b/web/src/api/asset_interfaces/metric/defaults.ts @@ -72,6 +72,7 @@ export const DEFAULT_CHART_CONFIG: IBusterMetricChartConfig = { pieDonutWidth: 40, pieMinimumSlicePercentage: 0, pieDisplayLabelAs: 'number', + pieSortBy: 'value', //METRIC metricColumnId: '', metricValueAggregate: 'sum', diff --git a/web/src/components/ui/charts/BusterChartComponent.tsx b/web/src/components/ui/charts/BusterChartComponent.tsx index 6e84624c6..aea19710b 100644 --- a/web/src/components/ui/charts/BusterChartComponent.tsx +++ b/web/src/components/ui/charts/BusterChartComponent.tsx @@ -9,12 +9,19 @@ import { useDatasetOptions } from './chartHooks'; export const BusterChartComponent: React.FC = ({ data: dataProp, barSortBy, + pieSortBy, pieMinimumSlicePercentage, trendlines, ...props }) => { - const { barGroupType, lineGroupType, columnLabelFormats, selectedChartType, selectedAxis } = - props; + const { + barGroupType, + columnMetadata, + lineGroupType, + columnLabelFormats, + selectedChartType, + selectedAxis + } = props; const { datasetOptions, @@ -32,7 +39,9 @@ export const BusterChartComponent: React.FC = ( columnLabelFormats, barGroupType, lineGroupType, - trendlines + trendlines, + pieSortBy, + columnMetadata }); const chartProps: BusterChartComponentProps = useMemo( diff --git a/web/src/components/ui/charts/chartHooks/useDatasetOptions/config.ts b/web/src/components/ui/charts/chartHooks/useDatasetOptions/config.ts index 766238b98..78de300e2 100644 --- a/web/src/components/ui/charts/chartHooks/useDatasetOptions/config.ts +++ b/web/src/components/ui/charts/chartHooks/useDatasetOptions/config.ts @@ -41,6 +41,7 @@ export const DATASET_IDS = { raw: 'raw', relativeStack: 'relative-stack', sortedByBar: 'sorted-by-bar', + sortedByValue: 'sorted-by-value', rawWithDateNotDelimited: 'raw-with-date-not-delimited', pieMinimum: (yAxisKey: string) => `pie-minimum-${yAxisKey}`, //TRENDLINE IDS diff --git a/web/src/components/ui/charts/chartHooks/useDatasetOptions/datasetHelpers_BarLinePie.ts b/web/src/components/ui/charts/chartHooks/useDatasetOptions/datasetHelpers_BarLinePie.ts index 2c7858c4a..7d38ccb3f 100644 --- a/web/src/components/ui/charts/chartHooks/useDatasetOptions/datasetHelpers_BarLinePie.ts +++ b/web/src/components/ui/charts/chartHooks/useDatasetOptions/datasetHelpers_BarLinePie.ts @@ -3,42 +3,69 @@ import { type BusterChartProps, type ChartType, - type BarSortBy + type BarSortBy, + PieSortBy } from '@/api/asset_interfaces/metric/charts'; import { createDayjsDate } from '@/lib/date'; import { extractFieldsFromChain, appendToKeyValueChain } from './groupingHelpers'; import { DATASET_IDS, GROUPING_SEPARATOR } from './config'; import { DatasetOption } from './interfaces'; -import { DEFAULT_COLUMN_LABEL_FORMAT } from '@/api/asset_interfaces/metric'; +import { + ColumnMetaData, + DEFAULT_COLUMN_LABEL_FORMAT, + SimplifiedColumnType +} from '@/api/asset_interfaces/metric'; type DataItem = NonNullable[number]; export const sortLineBarData = ( data: NonNullable, + columnMetadata: NonNullable, xFieldSorts: string[], xFields: string[] ) => { if (xFieldSorts.length === 0) return data; + const columnMetadataRecord = columnMetadata.reduce>( + (acc, curr) => { + acc[curr.name] = curr; + return acc; + }, + {} + ); + const sortedData = [...data]; if (xFieldSorts.length > 0) { sortedData.sort((a, b) => { for (let i = 0; i < xFieldSorts.length; i++) { - const dateField = xFields[i]; + const field = xFields[i]; + const fieldType: SimplifiedColumnType = columnMetadataRecord[field]?.simple_type || 'text'; + //NUMBER CASE - if (typeof a[dateField] === 'number' && typeof b[dateField] === 'number') { - if (a[dateField] !== b[dateField]) { - return (a[dateField] as number) - (b[dateField] as number); + if ( + fieldType === 'number' || + (typeof a[field] === 'number' && typeof b[field] === 'number') + ) { + if (a[field] !== b[field]) { + return (a[field] as number) - (b[field] as number); } } + //DATE CASE - else { - const aDate = createDayjsDate(a[dateField] as string); - const bDate = createDayjsDate(b[dateField] as string); + else if (fieldType === 'date') { + const aDate = createDayjsDate(a[field] as string); + const bDate = createDayjsDate(b[field] as string); if (aDate.valueOf() !== bDate.valueOf()) { return aDate.valueOf() - bDate.valueOf(); } } + + //TEXT CASE + else { + if (a[field] !== b[field]) { + return String(a[field]).localeCompare(String(b[field])); + } + } } return 0; }); @@ -197,6 +224,7 @@ export const getLineBarPieDatasetOptions = ( selectedChartType: ChartType, pieMinimumSlicePercentage: number | undefined, barSortBy: BarSortBy | undefined, + pieSortBy: PieSortBy | undefined, yAxisKeys: string[], //only used this for pie charts xFieldDateSorts: string[], barGroupType: BusterChartProps['barGroupType'] | undefined, @@ -281,6 +309,21 @@ export const getLineBarPieDatasetOptions = ( }); } + if (selectedChartType === 'pie' && pieSortBy === 'value') { + const lastDataset = datasets[datasets.length - 1]; + const lastSource = lastDataset.source as (string | number | Date | null)[][]; + const sortedData = [...lastSource] + .sort((a, b) => { + return (Number(b[1]) || 0) - (Number(a[1]) || 0); + }) + .reverse(); + datasets.push({ + id: DATASET_IDS.sortedByValue, + dimensions, + source: sortedData + }); + } + if (selectedChartType === 'bar' && barSortBy && barSortBy?.some((y) => y !== 'none')) { // Sort the processed data based on the y-axis values at their respective indices // Pre-calculate indices and directions to avoid repeated lookups diff --git a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.test.tsx b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.test.tsx index be2af53b8..fe82b3a4e 100644 --- a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.test.tsx +++ b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.test.tsx @@ -42,7 +42,8 @@ describe('useDatasetOptions - bar chart - all values are present', () => { pieMinimumSlicePercentage: undefined, barGroupType: undefined, lineGroupType: undefined, - trendlines: undefined + trendlines: undefined, + columnMetadata: [] }; it('should return the correct structure - has category', () => { @@ -127,7 +128,8 @@ describe('useDatasetOptions - bar chart ', () => { pieMinimumSlicePercentage: undefined, barGroupType: undefined, lineGroupType: undefined, - trendlines: undefined + trendlines: undefined, + columnMetadata: [] }; it('should return the correct structure', () => { @@ -201,7 +203,8 @@ describe('useDatasetOptions - bar chart - some numerical values are null', () => pieMinimumSlicePercentage: undefined, barGroupType: undefined, lineGroupType: undefined, - trendlines: undefined + trendlines: undefined, + columnMetadata: [] }; it('should process bar chart data correctly', () => { @@ -379,7 +382,8 @@ describe('useDatasetOptions - bar chart - some string values are null', () => { pieMinimumSlicePercentage: undefined, barGroupType: undefined, lineGroupType: undefined, - trendlines: undefined + trendlines: undefined, + columnMetadata: [] }; it('should process bar chart data correctly', () => { diff --git a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.tsx b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.tsx index 357931493..483a31004 100644 --- a/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.tsx +++ b/web/src/components/ui/charts/chartHooks/useDatasetOptions/useDatasetOptions.tsx @@ -10,7 +10,8 @@ import { ScatterAxis, Trendline, ComboChartAxis, - type IColumnLabelFormat + type IColumnLabelFormat, + PieSortBy } from '@/api/asset_interfaces/metric/charts'; import uniq from 'lodash/uniq'; import { @@ -46,6 +47,7 @@ type DatasetHookResult = { type DatasetHookParams = { data: NonNullable; barSortBy?: BarSortBy; + pieSortBy?: PieSortBy; groupByMethod?: BusterChartProps['groupByMethod']; selectedAxis: ChartEncodes; selectedChartType: ChartType; @@ -54,6 +56,7 @@ type DatasetHookParams = { barGroupType: BusterChartProps['barGroupType'] | undefined; lineGroupType: BusterChartProps['lineGroupType']; trendlines: Trendline[] | undefined; + columnMetadata: NonNullable; }; const defaultYAxis2 = [] as string[]; @@ -68,7 +71,9 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult pieMinimumSlicePercentage, barGroupType, lineGroupType, - trendlines + trendlines, + pieSortBy, + columnMetadata } = params; const { x: xFields, @@ -100,13 +105,19 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult ); }, [xFieldsString, columnLabelFormats]); - //WILL ONLY BE USED FOR BAR CHART + //WILL ONLY BE USED FOR BAR AND PIE CHART const xFieldSorts = useMemo(() => { - if (isScatter) return []; - if (isPieChart) return []; - if (isBarChart && barSortBy && barSortBy?.some((y) => y !== 'none')) return []; + if (isPieChart) { + if (pieSortBy === 'key') return xFieldColumnLabelFormatColumnTypes; + return []; + } + + if (isBarChart) { + if (barSortBy && barSortBy?.some((y) => y !== 'none')) return []; + } + return xFieldColumnLabelFormatColumnTypes.filter((columnType) => columnType === 'date'); - }, [xFieldColumnLabelFormatColumnTypes, isPieChart, isBarChart, isScatter, barSortBy]); + }, [xFieldColumnLabelFormatColumnTypes, pieSortBy, isPieChart, isBarChart, isScatter, barSortBy]); const xFieldSortsString = useMemo(() => xFieldSorts.join(','), [xFieldSorts]); @@ -116,7 +127,7 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult const sortedAndLimitedData = useMemo(() => { if (isScatter) return downsampleScatterData(data); - return sortLineBarData(data, xFieldSorts, xFields); + return sortLineBarData(data, columnMetadata, xFieldSorts, xFields); }, [data, xFieldSortsString, xFieldsString, isScatter]); const { dataMap, xValuesSet, categoriesSet } = useMemo(() => { @@ -218,6 +229,7 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult selectedChartType, pieMinimumSlicePercentage, barSortBy, + pieSortBy, yAxisKeys, xFieldSorts, barGroupType, @@ -230,6 +242,7 @@ export const useDatasetOptions = (params: DatasetHookParams): DatasetHookResult dimensions, yAxisKeys, barSortBy, + pieSortBy, pieMinimumSlicePercentage, measureFields, isScatter, diff --git a/web/src/components/ui/charts/interfaces/chartComponentInterfaces.ts b/web/src/components/ui/charts/interfaces/chartComponentInterfaces.ts index e5fec7187..414eabb57 100644 --- a/web/src/components/ui/charts/interfaces/chartComponentInterfaces.ts +++ b/web/src/components/ui/charts/interfaces/chartComponentInterfaces.ts @@ -19,7 +19,7 @@ export interface BusterChartTypeComponentProps export interface BusterChartComponentProps extends Omit< Required, - 'selectedAxis' | 'barSortBy' | 'trendlines' | 'data' + 'selectedAxis' | 'barSortBy' | 'pieSortBy' | 'trendlines' | 'data' >, ReturnType { selectedAxis: ChartEncodes; diff --git a/web/src/components/ui/charts/stories/BusterChart.BarChart.stories.tsx b/web/src/components/ui/charts/stories/BusterChart.BarChart.stories.tsx index 410ae21e4..e175caf0d 100644 --- a/web/src/components/ui/charts/stories/BusterChart.BarChart.stories.tsx +++ b/web/src/components/ui/charts/stories/BusterChart.BarChart.stories.tsx @@ -422,3 +422,71 @@ export const LargeDatasetWithDualYAxis: Story = { ); } }; + +export const WithSorting: Story = { + args: { + ...Default.args, + barAndLineAxis: { + x: ['category'], + y: ['sales'], + category: [] + }, + barSortBy: ['asc'] + } +}; + +export const WithDatesInXAxis: Story = { + args: { + ...Default.args, + data: Array.from({ length: 7 }, (_, index) => ({ + date: faker.date.past({ years: 1 }).toISOString(), + sales: faker.number.int({ min: 1000, max: 10000 }) + })), + barAndLineAxis: { + x: ['date'], + y: ['sales'], + category: [] + }, + // barSortBy: ['asc'], + columnLabelFormats: { + date: { + columnType: 'date', + style: 'date', + dateFormat: 'LL' + } satisfies IColumnLabelFormat, + sales: { + columnType: 'number', + style: 'currency', + currency: 'USD' + } satisfies IColumnLabelFormat + } + } +}; + +export const WithDatesInXAxisAndSorting: Story = { + args: { + ...Default.args, + data: Array.from({ length: 7 }, (_, index) => ({ + date: faker.date.past({ years: 1 }).toISOString(), + sales: faker.number.int({ min: 1000, max: 10000 }) + })), + barAndLineAxis: { + x: ['date'], + y: ['sales'], + category: [] + }, + barSortBy: ['asc'], + columnLabelFormats: { + date: { + columnType: 'date', + style: 'date', + dateFormat: 'LL' + } satisfies IColumnLabelFormat, + sales: { + columnType: 'number', + style: 'currency', + currency: 'USD' + } satisfies IColumnLabelFormat + } + } +}; diff --git a/web/src/components/ui/charts/stories/BusterChart.PieChart.stories.tsx b/web/src/components/ui/charts/stories/BusterChart.PieChart.stories.tsx index d83ba36a8..01789f78d 100644 --- a/web/src/components/ui/charts/stories/BusterChart.PieChart.stories.tsx +++ b/web/src/components/ui/charts/stories/BusterChart.PieChart.stories.tsx @@ -5,6 +5,7 @@ import { ChartType } from '../../../../api/asset_interfaces/metric/charts/enum'; import { IColumnLabelFormat } from '../../../../api/asset_interfaces/metric/charts/columnLabelInterfaces'; import { generatePieChartData } from '../../../../mocks/chart/chartMocks'; import { sharedMeta } from './BusterChartShared'; +import { faker } from '@faker-js/faker'; type PieChartData = ReturnType; @@ -170,3 +171,88 @@ export const ResizableContainer: Story = { } } }; + +export const WithSortingByKey: Story = { + args: { + selectedChartType: ChartType.Pie, + data: Array.from({ length: 8 }, (_, index) => ({ + segment: faker.word.adjective(), + value: + index === 1 || index === 5 + ? faker.number.int({ min: 10, max: 25 }) + : faker.number.int({ min: 50, max: 150 }) + })), + pieChartAxis: { + x: ['segment'], + y: ['value'] + }, + pieSortBy: 'key', + columnMetadata: [ + { + name: 'segment', + simple_type: 'text', + min_value: 0, + max_value: 100, + unique_values: 10, + type: 'text' + }, + { + name: 'value', + simple_type: 'number', + min_value: 0, + max_value: 100, + unique_values: 10, + type: 'number' + } + ] + } +}; + +export const WithSortingByKeyWithDates: Story = { + args: { + selectedChartType: ChartType.Pie, + data: Array.from({ length: 8 }, (_, index) => ({ + date: faker.date.recent({ days: 180 }).toISOString(), + value: + index === 1 || index === 5 + ? faker.number.int({ min: 10, max: 25 }) + : faker.number.int({ min: 50, max: 150 }) + })), + pieChartAxis: { + x: ['date'], + y: ['value'] + }, + pieSortBy: 'key', + columnLabelFormats: { + date: { + columnType: 'date', + style: 'date' + } satisfies IColumnLabelFormat + }, + columnMetadata: [ + { + name: 'date', + simple_type: 'date', + min_value: 0, + max_value: 100, + unique_values: 10, + type: 'date' + }, + { + name: 'value', + simple_type: 'number', + min_value: 0, + max_value: 100, + unique_values: 10, + type: 'number' + } + ] + } +}; + +export const WithSortingByValue: Story = { + args: { + ...WithSortingByKey.args!, + pieSortBy: 'value' + } +}; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditBarAxisSorting.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditBarAxisSorting.tsx index b0c326bb0..e5c23b3fe 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditBarAxisSorting.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditBarAxisSorting.tsx @@ -8,21 +8,24 @@ import { ChartBarTrendDown, ChartBarTrendUp } from '@/components/ui/icons/NucleoIconFilled'; +import { BarChartSortAscIcon } from '@/components/ui/icons/customIcons/BarChartSortAscIcon'; +import { BarChartSortNoneIcon } from '@/components/ui/icons/customIcons/BarChart_NoSort'; +import { BarChartSortDescIcon } from '@/components/ui/icons/customIcons/BarChartSortDescIcon'; import { useMemoizedFn } from '@/hooks'; const options: SegmentedItem[] = [ { value: 'none', tooltip: 'No sorting', - icon: + icon: }, { - icon: , + icon: , value: 'asc', tooltip: 'Sort ascending' }, { - icon: , + icon: , value: 'desc', tooltip: 'Sort descending' } diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieSorting.stories.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieSorting.stories.tsx new file mode 100644 index 000000000..35df5f796 --- /dev/null +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieSorting.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { EditPieSorting } from './EditPieSorting'; +import { fn } from '@storybook/test'; + +const meta: Meta = { + title: 'Controllers/MetricController/EditPieSorting', + component: EditPieSorting, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryObj; + +export const SortByKey: Story = { + args: { + pieSortBy: 'key', + onUpdateChartConfig: fn() + } +}; + +export const SortByValue: Story = { + args: { + pieSortBy: 'value', + onUpdateChartConfig: fn() + } +}; + +export const NoSorting: Story = { + args: { + pieSortBy: null, + onUpdateChartConfig: fn() + } +}; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieSorting.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieSorting.tsx new file mode 100644 index 000000000..3cb9e369a --- /dev/null +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditPieSorting.tsx @@ -0,0 +1,55 @@ +import { IBusterMetricChartConfig } from '@/api/asset_interfaces'; +import React, { useMemo } from 'react'; +import { LabelAndInput } from '../Common'; +import { PieSortBy } from '@/api/asset_interfaces/metric/charts'; +import { AppSegmented, SegmentedItem } from '@/components/ui/segmented'; +import { SortAlphaAscending, SortNumAscending, Empty } from '@/components/ui/icons'; +import { useMemoizedFn } from '@/hooks'; + +const options: SegmentedItem | 'none'>[] = [ + { + value: 'key', + tooltip: 'Sort by key', + icon: + }, + { + icon: , + value: 'value', + tooltip: 'Sort by value' + }, + { + icon: , + value: 'none', + tooltip: 'No sorting' + } +]; + +export const EditPieSorting: React.FC<{ + pieSortBy: IBusterMetricChartConfig['pieSortBy']; + onUpdateChartConfig: (v: Partial) => void; +}> = React.memo(({ pieSortBy, onUpdateChartConfig }) => { + const selectedOption = useMemo(() => { + return ( + options.find((option) => { + return pieSortBy === option.value; + })?.value || 'none' + ); + }, [pieSortBy]); + + const onChange = useMemoizedFn((value: SegmentedItem | 'none'>) => { + if (value.value === 'none') { + onUpdateChartConfig({ pieSortBy: null }); + } else { + onUpdateChartConfig({ pieSortBy: value.value }); + } + }); + + return ( + +
+ +
+
+ ); +}); +EditPieSorting.displayName = 'EditPieSorting'; diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditScatterDotSize.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditScatterDotSize.tsx index c6f51f2f8..42288bd05 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditScatterDotSize.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditScatterDotSize.tsx @@ -1,5 +1,5 @@ import { IBusterMetricChartConfig } from '@/api/asset_interfaces'; -import { ScatterAxis } from '@/api/asset_interfaces/metric/charts'; +import { ChartEncodes, ScatterAxis } from '@/api/asset_interfaces/metric/charts'; import { Slider } from '@/components/ui/slider'; import React from 'react'; import isEmpty from 'lodash/isEmpty'; @@ -8,10 +8,10 @@ import { LabelAndInput } from '../Common'; export const EditScatterDotSize: React.FC<{ scatterDotSize: IBusterMetricChartConfig['scatterDotSize']; - scatterAxis: ScatterAxis; + selectedAxis: ChartEncodes; onUpdateChartConfig: (config: Partial) => void; -}> = React.memo(({ scatterDotSize, scatterAxis, onUpdateChartConfig }) => { - const hasSize = !isEmpty(scatterAxis.size); +}> = React.memo(({ scatterDotSize, selectedAxis, onUpdateChartConfig }) => { + const hasSize = !isEmpty((selectedAxis as ScatterAxis).size); const defaultValue = hasSize ? scatterDotSize : scatterDotSize[0]; const onChange = useMemoizedFn((v: number[]) => { diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditShowDataLabels.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditShowDataLabels.tsx index 14113a72a..84c5417cb 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditShowDataLabels.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/EditShowDataLabels.tsx @@ -6,13 +6,13 @@ import { WarningIcon } from '../Common/WarningIcon'; export const EditShowDataLabels: React.FC<{ showDataLabels: boolean; rowCount: number; - onUpdateColumnSettingConfig: (v: boolean) => void; -}> = React.memo(({ showDataLabels, rowCount, onUpdateColumnSettingConfig }) => { + onUpdateDataLabel: (v: boolean) => void; +}> = React.memo(({ showDataLabels, rowCount, onUpdateDataLabel }) => { return (
- +
); diff --git a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/StylingAppStyling.tsx b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/StylingAppStyling.tsx index 39476997c..f5ad17fd3 100644 --- a/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/StylingAppStyling.tsx +++ b/web/src/controllers/MetricController/MetricViewChart/MetricEditController/MetricStylingApp/StylingAppStyling/StylingAppStyling.tsx @@ -31,41 +31,20 @@ import { import { StylingAppStylingNotSupported } from './StylingAppStylingNotSupported'; import { EditScatterDotSize } from './EditScatterDotSize'; import { useUpdateMetricChart } from '@/context/Metrics'; +import { EditPieSorting } from './EditPieSorting'; const sectionClass = 'flex w-full flex-col space-y-3 my-3'; const UNSUPPORTED_CHART_TYPES: ChartType[] = [ChartType.Table, ChartType.Metric]; -export const StylingAppStyling: React.FC<{ - className?: string; - columnSettings: IBusterMetricChartConfig['columnSettings']; - showLegend: IBusterMetricChartConfig['showLegend']; - gridLines: IBusterMetricChartConfig['gridLines']; - yAxisShowAxisLabel: IBusterMetricChartConfig['yAxisShowAxisLabel']; - yAxisShowAxisTitle: IBusterMetricChartConfig['yAxisShowAxisTitle']; - barSortBy: IBusterMetricChartConfig['barSortBy']; - selectedChartType: IBusterMetricChartConfig['selectedChartType']; - lineGroupType: IBusterMetricChartConfig['lineGroupType']; - barGroupType: IBusterMetricChartConfig['barGroupType']; - pieChartAxis: IBusterMetricChartConfig['pieChartAxis']; - yAxisScaleType: IBusterMetricChartConfig['yAxisScaleType']; - y2AxisScaleType: IBusterMetricChartConfig['y2AxisScaleType']; - showLegendHeadline: IBusterMetricChartConfig['showLegendHeadline']; - goalLines: IBusterMetricChartConfig['goalLines']; - trendlines: IBusterMetricChartConfig['trendlines']; - pieDisplayLabelAs: IBusterMetricChartConfig['pieDisplayLabelAs']; - pieLabelPosition: IBusterMetricChartConfig['pieLabelPosition']; - pieDonutWidth: IBusterMetricChartConfig['pieDonutWidth']; - pieInnerLabelAggregate: IBusterMetricChartConfig['pieInnerLabelAggregate']; - pieInnerLabelTitle: IBusterMetricChartConfig['pieInnerLabelTitle']; - pieShowInnerLabel: IBusterMetricChartConfig['pieShowInnerLabel']; - pieMinimumSlicePercentage: IBusterMetricChartConfig['pieMinimumSlicePercentage']; - scatterDotSize: IBusterMetricChartConfig['scatterDotSize']; - selectedAxis: ChartEncodes; - columnMetadata: ColumnMetaData[]; - rowCount: number; - columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats']; - barShowTotalAtTop: IBusterMetricChartConfig['barShowTotalAtTop']; -}> = ({ +export const StylingAppStyling: React.FC< + { + className?: string; + } & Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] +> = ({ className = '', columnSettings, showLegend, @@ -94,7 +73,8 @@ export const StylingAppStyling: React.FC<{ columnLabelFormats, barShowTotalAtTop, yAxisShowAxisTitle, - rowCount + rowCount, + pieSortBy }) => { const { onUpdateMetricChartConfig } = useUpdateMetricChart(); @@ -167,6 +147,7 @@ export const StylingAppStyling: React.FC<{ selectedAxis={selectedAxis} columnLabelFormats={columnLabelFormats} barShowTotalAtTop={barShowTotalAtTop} + pieSortBy={pieSortBy} /> {selectedChartType === 'pie' && ( @@ -201,22 +182,19 @@ export const StylingAppStyling: React.FC<{ ); }; -const GlobalSettings: React.FC<{ - className: string; - showLegend: IBusterMetricChartConfig['showLegend']; - gridLines: IBusterMetricChartConfig['gridLines']; - columnSettings: IBusterMetricChartConfig['columnSettings']; - yAxisShowAxisTitle: IBusterMetricChartConfig['yAxisShowAxisTitle']; - yAxisShowAxisLabel: IBusterMetricChartConfig['yAxisShowAxisLabel']; - pieDisplayLabelAs: IBusterMetricChartConfig['pieDisplayLabelAs']; - selectedChartType: IBusterMetricChartConfig['selectedChartType']; - pieLabelPosition: IBusterMetricChartConfig['pieLabelPosition']; - selectedAxis: ChartEncodes; - rowCount: number; - onUpdateChartConfig: (chartConfig: Partial) => void; - onUpdateDataLabel: (v: boolean) => void; - onUpdateYAxis: (v: boolean) => void; -}> = ({ +const GlobalSettings: React.FC< + { + className: string; + columnSettings: IBusterMetricChartConfig['columnSettings']; + yAxisShowAxisTitle: IBusterMetricChartConfig['yAxisShowAxisTitle']; + yAxisShowAxisLabel: IBusterMetricChartConfig['yAxisShowAxisLabel']; + } & Parameters[0] & + Parameters[0] & + Omit[0], 'hideYAxis'> & + Parameters[0] & + Parameters[0] & + Omit[0], 'showDataLabels'> +> = ({ className = '', showLegend, selectedAxis, @@ -267,7 +245,7 @@ const GlobalSettings: React.FC<{ Component: ( ) @@ -313,24 +291,23 @@ const GlobalSettings: React.FC<{ ); }; -const ChartSpecificSettings: React.FC<{ - className: string; - columnSettings: IBusterMetricChartConfig['columnSettings']; - selectedChartType: IBusterMetricChartConfig['selectedChartType']; - barSortBy: IBusterMetricChartConfig['barSortBy']; - onUpdateChartConfig: (chartConfig: Partial) => void; - lineGroupType: IBusterMetricChartConfig['lineGroupType']; - barGroupType: IBusterMetricChartConfig['barGroupType']; - yAxisScaleType: IBusterMetricChartConfig['yAxisScaleType']; - y2AxisScaleType: IBusterMetricChartConfig['y2AxisScaleType']; - pieDonutWidth: IBusterMetricChartConfig['pieDonutWidth']; - pieMinimumSlicePercentage: IBusterMetricChartConfig['pieMinimumSlicePercentage']; - pieChartAxis: IBusterMetricChartConfig['pieChartAxis']; - scatterDotSize: IBusterMetricChartConfig['scatterDotSize']; - selectedAxis: ChartEncodes; - columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats']; - barShowTotalAtTop: IBusterMetricChartConfig['barShowTotalAtTop']; -}> = ({ +const ChartSpecificSettings: React.FC< + { + className: string; + columnSettings: IBusterMetricChartConfig['columnSettings']; + selectedChartType: IBusterMetricChartConfig['selectedChartType']; + columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats']; + } & Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] & + Parameters[0] +> = ({ className = '', barSortBy, onUpdateChartConfig, @@ -346,7 +323,8 @@ const ChartSpecificSettings: React.FC<{ scatterDotSize, selectedAxis, columnLabelFormats, - barShowTotalAtTop + barShowTotalAtTop, + pieSortBy }) => { const isBarChart = selectedChartType === 'bar'; const isComboChart = selectedChartType === 'combo'; @@ -374,6 +352,11 @@ const ChartSpecificSettings: React.FC<{ key: 'barSorting', Component: }, + { + enabled: isPieChart, + key: 'pieSorting', + Component: + }, { enabled: isBarChart && @@ -457,7 +440,7 @@ const ChartSpecificSettings: React.FC<{ Component: ( ) @@ -479,19 +462,13 @@ const ChartSpecificSettings: React.FC<{ ); }; -const EtcSettings: React.FC<{ - className: string; - selectedChartType: IBusterMetricChartConfig['selectedChartType']; - showLegendHeadline: IBusterMetricChartConfig['showLegendHeadline']; - goalLines: IBusterMetricChartConfig['goalLines']; - trendlines: IBusterMetricChartConfig['trendlines']; - selectedAxis: ChartEncodes; - columnMetadata: ColumnMetaData[]; - columnLabelFormats: IBusterMetricChartConfig['columnLabelFormats']; - lineGroupType: IBusterMetricChartConfig['lineGroupType']; - barGroupType: IBusterMetricChartConfig['barGroupType']; - onUpdateChartConfig: (chartConfig: Partial) => void; -}> = ({ +const EtcSettings: React.FC< + { + className: string; + } & Parameters[0] & + Parameters[0] & + Parameters[0] +> = ({ className = '', onUpdateChartConfig, selectedChartType, @@ -563,14 +540,13 @@ const EtcSettings: React.FC<{ ); }; -const PieSettings: React.FC<{ - className: string; - pieInnerLabelAggregate: IBusterMetricChartConfig['pieInnerLabelAggregate']; - pieShowInnerLabel: IBusterMetricChartConfig['pieShowInnerLabel']; - pieInnerLabelTitle: IBusterMetricChartConfig['pieInnerLabelTitle']; - pieDonutWidth: IBusterMetricChartConfig['pieDonutWidth']; - onUpdateChartConfig: (chartConfig: Partial) => void; -}> = React.memo( +const PieSettings: React.FC< + { + className: string; + pieDonutWidth: IBusterMetricChartConfig['pieDonutWidth']; + } & Parameters[0] & + Parameters[0] +> = React.memo( ({ className, pieInnerLabelAggregate,