mirror of https://github.com/buster-so/buster.git
more elegant parameters for styling app
This commit is contained in:
parent
8aa9ee8751
commit
dbd24125f7
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -72,6 +72,7 @@ export const DEFAULT_CHART_CONFIG: IBusterMetricChartConfig = {
|
|||
pieDonutWidth: 40,
|
||||
pieMinimumSlicePercentage: 0,
|
||||
pieDisplayLabelAs: 'number',
|
||||
pieSortBy: 'value',
|
||||
//METRIC
|
||||
metricColumnId: '',
|
||||
metricValueAggregate: 'sum',
|
||||
|
|
|
@ -9,12 +9,19 @@ import { useDatasetOptions } from './chartHooks';
|
|||
export const BusterChartComponent: React.FC<BusterChartRenderComponentProps> = ({
|
||||
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<BusterChartRenderComponentProps> = (
|
|||
columnLabelFormats,
|
||||
barGroupType,
|
||||
lineGroupType,
|
||||
trendlines
|
||||
trendlines,
|
||||
pieSortBy,
|
||||
columnMetadata
|
||||
});
|
||||
|
||||
const chartProps: BusterChartComponentProps = useMemo(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<BusterChartProps['data']>[number];
|
||||
|
||||
export const sortLineBarData = (
|
||||
data: NonNullable<BusterChartProps['data']>,
|
||||
columnMetadata: NonNullable<BusterChartProps['columnMetadata']>,
|
||||
xFieldSorts: string[],
|
||||
xFields: string[]
|
||||
) => {
|
||||
if (xFieldSorts.length === 0) return data;
|
||||
|
||||
const columnMetadataRecord = columnMetadata.reduce<Record<string, ColumnMetaData>>(
|
||||
(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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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<BusterChartProps['data']>;
|
||||
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<BusterChartProps['columnMetadata']>;
|
||||
};
|
||||
|
||||
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,
|
||||
|
|
|
@ -19,7 +19,7 @@ export interface BusterChartTypeComponentProps
|
|||
export interface BusterChartComponentProps
|
||||
extends Omit<
|
||||
Required<BusterChartRenderComponentProps>,
|
||||
'selectedAxis' | 'barSortBy' | 'trendlines' | 'data'
|
||||
'selectedAxis' | 'barSortBy' | 'pieSortBy' | 'trendlines' | 'data'
|
||||
>,
|
||||
ReturnType<typeof useDatasetOptions> {
|
||||
selectedAxis: ChartEncodes;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<typeof generatePieChartData>;
|
||||
|
||||
|
@ -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'
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<BarSortBy[0]>[] = [
|
||||
{
|
||||
value: 'none',
|
||||
tooltip: 'No sorting',
|
||||
icon: <ChartBarAxisX />
|
||||
icon: <BarChartSortNoneIcon />
|
||||
},
|
||||
{
|
||||
icon: <ChartBarTrendUp />,
|
||||
icon: <BarChartSortAscIcon />,
|
||||
value: 'asc',
|
||||
tooltip: 'Sort ascending'
|
||||
},
|
||||
{
|
||||
icon: <ChartBarTrendDown />,
|
||||
icon: <BarChartSortDescIcon />,
|
||||
value: 'desc',
|
||||
tooltip: 'Sort descending'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { EditPieSorting } from './EditPieSorting';
|
||||
import { fn } from '@storybook/test';
|
||||
|
||||
const meta: Meta<typeof EditPieSorting> = {
|
||||
title: 'Controllers/MetricController/EditPieSorting',
|
||||
component: EditPieSorting,
|
||||
parameters: {
|
||||
layout: 'centered'
|
||||
},
|
||||
tags: ['autodocs']
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EditPieSorting>;
|
||||
|
||||
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()
|
||||
}
|
||||
};
|
|
@ -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<NonNullable<PieSortBy> | 'none'>[] = [
|
||||
{
|
||||
value: 'key',
|
||||
tooltip: 'Sort by key',
|
||||
icon: <SortAlphaAscending />
|
||||
},
|
||||
{
|
||||
icon: <SortNumAscending />,
|
||||
value: 'value',
|
||||
tooltip: 'Sort by value'
|
||||
},
|
||||
{
|
||||
icon: <Empty />,
|
||||
value: 'none',
|
||||
tooltip: 'No sorting'
|
||||
}
|
||||
];
|
||||
|
||||
export const EditPieSorting: React.FC<{
|
||||
pieSortBy: IBusterMetricChartConfig['pieSortBy'];
|
||||
onUpdateChartConfig: (v: Partial<IBusterMetricChartConfig>) => void;
|
||||
}> = React.memo(({ pieSortBy, onUpdateChartConfig }) => {
|
||||
const selectedOption = useMemo(() => {
|
||||
return (
|
||||
options.find((option) => {
|
||||
return pieSortBy === option.value;
|
||||
})?.value || 'none'
|
||||
);
|
||||
}, [pieSortBy]);
|
||||
|
||||
const onChange = useMemoizedFn((value: SegmentedItem<NonNullable<PieSortBy> | 'none'>) => {
|
||||
if (value.value === 'none') {
|
||||
onUpdateChartConfig({ pieSortBy: null });
|
||||
} else {
|
||||
onUpdateChartConfig({ pieSortBy: value.value });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<LabelAndInput label="Sorting">
|
||||
<div className="flex justify-end">
|
||||
<AppSegmented options={options} value={selectedOption} onChange={onChange} type="button" />
|
||||
</div>
|
||||
</LabelAndInput>
|
||||
);
|
||||
});
|
||||
EditPieSorting.displayName = 'EditPieSorting';
|
|
@ -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<IBusterMetricChartConfig>) => 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[]) => {
|
||||
|
|
|
@ -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 (
|
||||
<LabelAndInput label={'Data labels'}>
|
||||
<div className="flex justify-end gap-x-2">
|
||||
<WarningIcon rowCount={rowCount} />
|
||||
<Switch defaultChecked={showDataLabels} onCheckedChange={onUpdateColumnSettingConfig} />
|
||||
<Switch defaultChecked={showDataLabels} onCheckedChange={onUpdateDataLabel} />
|
||||
</div>
|
||||
</LabelAndInput>
|
||||
);
|
||||
|
|
|
@ -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<typeof StylingAppStylingNotSupported>[0] &
|
||||
Parameters<typeof GlobalSettings>[0] &
|
||||
Parameters<typeof ChartSpecificSettings>[0] &
|
||||
Parameters<typeof EtcSettings>[0] &
|
||||
Parameters<typeof PieSettings>[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<IBusterMetricChartConfig>) => 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<typeof EditShowLegend>[0] &
|
||||
Parameters<typeof EditGridLines>[0] &
|
||||
Omit<Parameters<typeof EditHideYAxis>[0], 'hideYAxis'> &
|
||||
Parameters<typeof EditShowLabelPieAsPercentage>[0] &
|
||||
Parameters<typeof EditPieLabelLocation>[0] &
|
||||
Omit<Parameters<typeof EditShowDataLabels>[0], 'showDataLabels'>
|
||||
> = ({
|
||||
className = '',
|
||||
showLegend,
|
||||
selectedAxis,
|
||||
|
@ -267,7 +245,7 @@ const GlobalSettings: React.FC<{
|
|||
Component: (
|
||||
<EditShowDataLabels
|
||||
showDataLabels={mostPermissiveDataLabel}
|
||||
onUpdateColumnSettingConfig={onUpdateDataLabel}
|
||||
onUpdateDataLabel={onUpdateDataLabel}
|
||||
rowCount={rowCount}
|
||||
/>
|
||||
)
|
||||
|
@ -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<IBusterMetricChartConfig>) => 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<typeof EditBarRoundnessGlobal>[0] &
|
||||
Parameters<typeof EditBarSorting>[0] &
|
||||
Parameters<typeof EditPieSorting>[0] &
|
||||
Parameters<typeof EditGrouping>[0] &
|
||||
Parameters<typeof EditSmoothLinesGlobal>[0] &
|
||||
Parameters<typeof EditDotsOnLineGlobal>[0] &
|
||||
Parameters<typeof EditYAxisScaleGlobal>[0] &
|
||||
Parameters<typeof EditPieMinimumSlicePercentage>[0] &
|
||||
Parameters<typeof EditPieAppearance>[0] &
|
||||
Parameters<typeof EditScatterDotSize>[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: <EditBarSorting barSortBy={barSortBy} onUpdateChartConfig={onUpdateChartConfig} />
|
||||
},
|
||||
{
|
||||
enabled: isPieChart,
|
||||
key: 'pieSorting',
|
||||
Component: <EditPieSorting pieSortBy={pieSortBy} onUpdateChartConfig={onUpdateChartConfig} />
|
||||
},
|
||||
{
|
||||
enabled:
|
||||
isBarChart &&
|
||||
|
@ -457,7 +440,7 @@ const ChartSpecificSettings: React.FC<{
|
|||
Component: (
|
||||
<EditScatterDotSize
|
||||
scatterDotSize={scatterDotSize}
|
||||
scatterAxis={selectedAxis as ScatterAxis}
|
||||
selectedAxis={selectedAxis}
|
||||
onUpdateChartConfig={onUpdateChartConfig}
|
||||
/>
|
||||
)
|
||||
|
@ -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<IBusterMetricChartConfig>) => void;
|
||||
}> = ({
|
||||
const EtcSettings: React.FC<
|
||||
{
|
||||
className: string;
|
||||
} & Parameters<typeof EditShowHeadline>[0] &
|
||||
Parameters<typeof EditGoalLine>[0] &
|
||||
Parameters<typeof EditTrendline>[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<IBusterMetricChartConfig>) => void;
|
||||
}> = React.memo(
|
||||
const PieSettings: React.FC<
|
||||
{
|
||||
className: string;
|
||||
pieDonutWidth: IBusterMetricChartConfig['pieDonutWidth'];
|
||||
} & Parameters<typeof EditPieShowInnerLabel>[0] &
|
||||
Parameters<typeof EditPieInnerLabel>[0]
|
||||
> = React.memo(
|
||||
({
|
||||
className,
|
||||
pieInnerLabelAggregate,
|
||||
|
|
Loading…
Reference in New Issue