Skip to content

Commit

Permalink
Add stackGroups and zAxisMap to CategoricalChartState, fix types (#3848)
Browse files Browse the repository at this point in the history
## Description

Types!

## Related Issue

#3717

## Motivation and Context

One less `any`

## How Has This Been Tested?

TS, Jest

## Screenshots (if appropriate):

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

- [X] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] I have added a storybook story or extended an existing story to
show my changes
- [X] All new and existing tests passed.
  • Loading branch information
PavelVanecek authored Oct 13, 2023
1 parent 9f39919 commit e51814d
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 34 deletions.
67 changes: 56 additions & 11 deletions src/chart/generateCategoricalChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
parseDomainOfCategoryAxis,
getTooltipItem,
BarPosition,
AxisStackGroups,
} from '../util/ChartUtils';
import { detectReferenceElementsDomain } from '../util/DetectReferenceElementsDomain';
import { inRangeOfSector, polarToCartesian } from '../util/PolarUtils';
Expand Down Expand Up @@ -154,7 +155,11 @@ const getActiveCoordinate = (

const getDisplayedData = (
data: any[],
{ graphicalItems, dataStartIndex, dataEndIndex }: CategoricalChartState,
{
graphicalItems,
dataStartIndex,
dataEndIndex,
}: Pick<CategoricalChartState, 'graphicalItems' | 'dataStartIndex' | 'dataEndIndex'>,
item?: ReactElement,
): any[] => {
const itemsData = (graphicalItems || []).reduce((result, child) => {
Expand Down Expand Up @@ -296,7 +301,7 @@ export const getAxisMapByAxes = (
graphicalItems: ReadonlyArray<ReactElement>;
axisType: AxisType;
axisIdKey: string;
stackGroups: any;
stackGroups: AxisStackGroups;
dataStartIndex: number;
dataEndIndex: number;
},
Expand Down Expand Up @@ -468,7 +473,23 @@ export const getAxisMapByAxes = (
*/
const getAxisMapByItems = (
props: CategoricalChartProps,
{ graphicalItems, Axis, axisType, axisIdKey, stackGroups, dataStartIndex, dataEndIndex }: any,
{
graphicalItems,
Axis,
axisType,
axisIdKey,
stackGroups,
dataStartIndex,
dataEndIndex,
}: {
axisIdKey: string;
axisType?: AxisType;
Axis?: React.ComponentType<BaseAxisProps>;
graphicalItems: ReadonlyArray<ReactElement>;
stackGroups: AxisStackGroups;
dataStartIndex: number;
dataEndIndex: number;
},
): AxisMap => {
const { layout, children } = props;
const displayedData = getDisplayedData(props.data, {
Expand Down Expand Up @@ -558,7 +579,7 @@ const getAxisMap = (
axisType?: AxisType;
AxisComp?: React.ComponentType;
graphicalItems: ReadonlyArray<ReactElement>;
stackGroups: any;
stackGroups: AxisStackGroups;
dataStartIndex: number;
dataEndIndex: number;
},
Expand Down Expand Up @@ -759,6 +780,8 @@ export interface CategoricalChartState {

yAxisMap?: AxisMap;

zAxisMap?: AxisMap;

orderedTooltipTicks?: any;

tooltipAxis?: BaseAxisProps;
Expand Down Expand Up @@ -803,6 +826,8 @@ export interface CategoricalChartState {
prevStackOffset?: StackOffsetType;
prevMargin?: Margin;
prevChildren?: any;

stackGroups?: AxisStackGroups;
}

export type CategoricalChartFunc = (nextState: CategoricalChartState, event: any) => void;
Expand Down Expand Up @@ -848,6 +873,23 @@ export interface CategoricalChartProps {
tabIndex?: number;
}

type AxisObj = {
xAxis?: BaseAxisProps;
xAxisTicks?: Array<TickItem>;

yAxis?: BaseAxisProps;
yAxisTicks?: Array<TickItem>;

zAxis?: BaseAxisProps;
zAxisTicks?: Array<TickItem>;

angleAxis?: BaseAxisProps;
angleAxisTicks?: Array<TickItem>;

radiusAxis?: BaseAxisProps;
radiusAxisTicks?: Array<TickItem>;
};

export const generateCategoricalChart = ({
chartName,
GraphicalChild,
Expand All @@ -858,7 +900,7 @@ export const generateCategoricalChart = ({
formatAxisMap,
defaultProps,
}: CategoricalChartOptions) => {
const getFormatItems = (props: CategoricalChartProps, currentState: any): any[] => {
const getFormatItems = (props: CategoricalChartProps, currentState: CategoricalChartState): any[] => {
const { graphicalItems, stackGroups, offset, updateId, dataStartIndex, dataEndIndex } = currentState;
const { barSize, layout, barGap, barCategoryGap, maxBarSize: globalMaxBarSize } = props;
const { numericAxisName, cateAxisName } = getAxisNameByLayout(layout);
Expand All @@ -873,11 +915,14 @@ export const generateCategoricalChart = ({
const numericAxisId = item.props[`${numericAxisName}Id`];
// axisId of the categorical axis
const cateAxisId = item.props[`${cateAxisName}Id`];
const axisObj = axisComponents.reduce((result: any, entry: BaseAxisProps) => {

const axisObjInitialValue: AxisObj = {};

const axisObj: AxisObj = axisComponents.reduce((result: AxisObj, entry: BaseAxisProps): AxisObj => {
// map of axisId to axis for a specific axis type
const axisMap: AxisMap | undefined = currentState[`${entry.axisType}Map`];
const axisMap: AxisMap | undefined = currentState[`${entry.axisType}Map` as const];
// axisId of axis we are currently computing
const id = item.props[`${entry.axisType}Id`];
const id: string = item.props[`${entry.axisType}Id`];

/**
* tell the user in dev mode that their configuration is incorrect if we cannot find a match between
Expand All @@ -900,9 +945,9 @@ export const generateCategoricalChart = ({
[entry.axisType]: axis,
[`${entry.axisType}Ticks`]: getTicksOfAxis(axis),
};
}, {});
}, axisObjInitialValue);
const cateAxis = axisObj[cateAxisName];
const cateTicks = axisObj[`${cateAxisName}Ticks`];
const cateTicks = axisObj[`${cateAxisName}Ticks` as const];
const stackedData =
stackGroups &&
stackGroups[numericAxisId] &&
Expand Down Expand Up @@ -990,7 +1035,7 @@ export const generateCategoricalChart = ({
const { children, layout, stackOffset, data, reverseStackOrder } = props;
const { numericAxisName, cateAxisName } = getAxisNameByLayout(layout);
const graphicalItems = findAllByType(children, GraphicalChild);
const stackGroups = getStackGroupsByAxisId(
const stackGroups: AxisStackGroups = getStackGroupsByAxisId(
data,
graphicalItems,
`${numericAxisName}Id`,
Expand Down
26 changes: 16 additions & 10 deletions src/util/ChartUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export const getBarSizeList = ({
stackGroups = {},
}: {
barSize: number | string;
stackGroups: any;
stackGroups: AxisStackGroups;
}): Record<string, ReadonlyArray<BarSetup>> => {
if (!stackGroups) {
return {};
Expand All @@ -229,7 +229,7 @@ export const getBarSizeList = ({
for (let j = 0, sLen = stackIds.length; j < sLen; j++) {
const { items, cateAxisId } = sgs[stackIds[j]];

const barItems = items.filter((item: any) => getDisplayName(item.type).indexOf('Bar') >= 0);
const barItems = items.filter(item => getDisplayName(item.type).indexOf('Bar') >= 0);

if (barItems && barItems.length) {
const { barSize: selfSize } = barItems[0].props;
Expand Down Expand Up @@ -934,20 +934,22 @@ export const getStackedData = (
};

type AxisId = string;
type StackId = string | number | symbol;
export type StackId = string | number | symbol;

export type ParentStackGroup = {
hasStack: boolean;
stackGroups: Record<StackId, ChildStackGroup>;
};

export type ChildStackGroup = {
export type GenericChildStackGroup<T> = {
numericAxisId: string;
cateAxisId: string;
items: Array<ReactElement>;
stackedData?: ReadonlyArray<Series<Record<string, unknown>, string>>;
stackedData?: ReadonlyArray<T>;
};

export type ChildStackGroup = GenericChildStackGroup<Series<Record<string, unknown>, string>>;

export type AxisStackGroups = Record<AxisId, ParentStackGroup>;

export const getStackGroupsByAxisId = (
Expand Down Expand Up @@ -1149,7 +1151,7 @@ export const getBaseValueOfBar = ({

export const getStackedDataOfItem = <StackedData>(
item: ReactElement,
stackGroups: Record<StackId, { items: ReadonlyArray<ReactElement>; stackedData: Record<number, StackedData> }>,
stackGroups: Record<StackId, GenericChildStackGroup<StackedData>>,
): StackedData | null => {
const { stackId } = item.props;

Expand All @@ -1165,23 +1167,27 @@ export const getStackedDataOfItem = <StackedData>(
return null;
};

const getDomainOfSingle = (data: Array<any>) =>
const getDomainOfSingle = (data: Array<Array<any>>): number[] =>
data.reduce(
(result, entry) => [
(result: number[], entry: Array<any>): number[] => [
_.min(entry.concat([result[0]]).filter(isNumber)),
_.max(entry.concat([result[1]]).filter(isNumber)),
],
[Infinity, -Infinity],
);

export const getDomainOfStackGroups = (stackGroups: any, startIndex: number, endIndex: number) =>
export const getDomainOfStackGroups = (
stackGroups: Record<StackId, ChildStackGroup>,
startIndex: number,
endIndex: number,
) =>
Object.keys(stackGroups)
.reduce(
(result, stackId) => {
const group = stackGroups[stackId];
const { stackedData } = group;
const domain = stackedData.reduce(
(res: [number, number], entry: any) => {
(res: [number, number], entry) => {
const s = getDomainOfSingle(entry.slice(startIndex, endIndex + 1));

return [Math.min(res[0], s[0]), Math.max(res[1], s[1])];
Expand Down
18 changes: 14 additions & 4 deletions test/chart/generateCategoricalChart.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement } from 'react';
import { getAxisMapByAxes, CategoricalChartProps } from '../../src/chart/generateCategoricalChart';
import { AxisStackGroups } from '../../src/util/ChartUtils';

const data = [
{
Expand Down Expand Up @@ -41,7 +42,7 @@ const data = [
];

describe('generateCategoricalChart', () => {
const graphicalItems: ReadonlyArray<ReactElement> = [
const graphicalItems: Array<ReactElement> = [
// @ts-expect-error this isn't a proper ReactElement
{
props: {
Expand Down Expand Up @@ -120,9 +121,18 @@ describe('generateCategoricalChart', () => {
height: 500,
};

const stackGroups = [
{ hasStack: false, stackGroups: { _stackId_49: { cateAxisId: 'xAxisId', items: graphicalItems } } },
];
const stackGroups: AxisStackGroups = {
'0': {
hasStack: false,
stackGroups: {
_stackId_49: {
cateAxisId: 'xAxisId',
items: graphicalItems,
numericAxisId: '',
},
},
},
};

const input = {
axes: xAxes,
Expand Down
33 changes: 24 additions & 9 deletions test/util/ChartUtils/getStackedDataOfItem.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReactElement } from 'react';
import { getStackedDataOfItem } from '../../../src/util/ChartUtils';
import { GenericChildStackGroup, StackId, getStackedDataOfItem } from '../../../src/util/ChartUtils';

function makeItem<T>(stackId: T): ReactElement {
// @ts-expect-error incomplete mock of ReactElement
Expand All @@ -8,6 +8,21 @@ function makeItem<T>(stackId: T): ReactElement {
};
}

function createStackGroups<T>(
key: PropertyKey,
stackedData: ReadonlyArray<T>,
items: Array<ReactElement>,
): Record<StackId, GenericChildStackGroup<T>> {
return {
[key]: {
stackedData,
items,
numericAxisId: '',
cateAxisId: '',
},
};
}

describe('getStackedDataOfItem', () => {
it('should return null if stackId is undefined', () => {
// @ts-expect-error incomplete mock of ReactElement
Expand All @@ -23,17 +38,17 @@ describe('getStackedDataOfItem', () => {

it('should return undefined if stack group is undefined', () => {
const item = makeItem<string>('a');
const stackedData: Array<unknown> = [];
const stackGroups = { a: { stackedData, items: [item] } };
const stackedData: Array<string> = [];
const stackGroups = createStackGroups('a', stackedData, [item]);
const result = getStackedDataOfItem(item, stackGroups);
expect(result).toBe(undefined);
});

it('should return null if stack group has empty items', () => {
const item = makeItem<string>('a');
const group = Symbol('my mock group');
const items: Array<typeof item> = [];
const stackGroups = { a: { stackedData: [group], items } };
const items: Array<ReactElement> = [];
const stackGroups = createStackGroups('a', [group], items);
const result = getStackedDataOfItem(item, stackGroups);
expect(result).toBe(null);
});
Expand All @@ -42,31 +57,31 @@ describe('getStackedDataOfItem', () => {
const item1 = makeItem<string>('a');
const item2 = makeItem<string>('a');
const group = Symbol('my mock group');
const stackGroups = { a: { stackedData: [group], items: [item1] } };
const stackGroups = createStackGroups('a', [group], [item1]);
const result = getStackedDataOfItem(item2, stackGroups);
expect(result).toBe(null);
});

it('should return stackedData if stack group contains the item matched by string', () => {
const item = makeItem<string>('a');
const group = Symbol('my mock group');
const stackGroups = { a: { stackedData: [group], items: [item] } };
const stackGroups = createStackGroups('a', [group], [item]);
const result = getStackedDataOfItem(item, stackGroups);
expect(result).toBe(group);
});

it('should return stackedData if stack group contains the item matched by number', () => {
const item = makeItem<number>(7);
const group = Symbol('my mock group');
const stackGroups = { 7: { stackedData: [group], items: [item] } };
const stackGroups = createStackGroups(7, [group], [item]);
const result = getStackedDataOfItem(item, stackGroups);
expect(result).toBe(group);
});

it('should return null if stack group contains the item matched by symbol', () => {
const item = makeItem<symbol>(Symbol.for('mock stack ID'));
const group = Symbol('my mock group');
const stackGroups = { [Symbol.for('mock stack ID')]: { stackedData: [group], items: [item] } };
const stackGroups = createStackGroups(Symbol.for('mock stack ID'), [group], [item]);
const result = getStackedDataOfItem(item, stackGroups);
expect(result).toBe(null);
});
Expand Down
9 changes: 9 additions & 0 deletions test/util/DataUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ describe('is functions', () => {
expect(isNumber(value)).toBe(result);
});
});

it('should allow type refinement', () => {
const arr: unknown[] = ['a', 1, false, NaN];
// @ts-expect-error typescript should highlight this - invalid assignment
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const invalid: number[] = arr;
const result: number[] = arr.filter(isNumber);
expect(result).toEqual([1]);
});
});

describe('isNumOrStr', () => {
Expand Down

0 comments on commit e51814d

Please sign in to comment.