Skip to content

Commit

Permalink
feat(react-chart): rotated chart (#2089)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The `x`, `y`, and `y1` properties in series points' coordinates have been renamed to `arg`, `val`, and `startVal` respectively. The old names were unsuitable in the case when the chart was rotated.
  • Loading branch information
DmitryBogomolov authored and Krijovnick committed Jul 2, 2019
1 parent 989098e commit def0662
Show file tree
Hide file tree
Showing 99 changed files with 1,626 additions and 799 deletions.
27 changes: 17 additions & 10 deletions packages/dx-chart-core/src/plugins/animation/computeds.test.ts
Expand Up @@ -14,38 +14,45 @@ describe('Animation styles', () => {
});

describe('#getAreaAnimationStyle', () => {
const yScale = () => 4;
yScale.copy = () => yScale;
yScale.clamp = () => yScale;
const scale = () => 4;
scale.copy = () => scale;
scale.clamp = () => scale;

it('should return style', () => {
expect(getAreaAnimationStyle({ yScale } as any)).toEqual({
expect(getAreaAnimationStyle(false, { yScale: scale } as any)).toEqual({
animation: 'animation_transform 1s',
transformOrigin: '0px 4px',
});
});

it('should return rotated style', () => {
expect(getAreaAnimationStyle(true, { xScale: scale } as any)).toEqual({
animation: 'animation_transform 1s',
transformOrigin: '4px 0px',
});
});
});

describe('#getPieAnimationStyle', () => {
it('should return style', () => {
expect(getPieAnimationStyle({} as any, { index: 3 } as any)).toEqual({
expect(getPieAnimationStyle({} as any, {} as any, { index: 3 } as any)).toEqual({
animation: 'animation_pie 1s',
});
});
});

describe('#getScatterAnimationStyle', () => {
it('should return style', () => {
expect(getScatterAnimationStyle({} as any)).toEqual({
expect(getScatterAnimationStyle({} as any, {} as any)).toEqual({
animation: 'animation_scatter 1.6s',
});
});
});

describe('style element generation', () => {
it('should reuse single "style" element', () => {
getScatterAnimationStyle({} as any);
getScatterAnimationStyle({} as any);
getScatterAnimationStyle({} as any, {} as any);
getScatterAnimationStyle({} as any, {} as any);

expect(head.getElementsByTagName('style').length).toEqual(1);
expect(head.getElementsByTagName('style')[0].textContent).toEqual(
Expand All @@ -60,13 +67,13 @@ describe('#buildAnimatedStyleGetter', () => {
it('should create function', () => {
const getAnimationStyle = jest.fn().mockReturnValue({ animation: 'test' });

expect(buildAnimatedStyleGetter(
expect(buildAnimatedStyleGetter(true)(
{ style: 'base' }, getAnimationStyle, 'test-scales' as any, 'test-point' as any,
)).toEqual({
style: 'base',
animation: 'test',
});
expect(getAnimationStyle)
.toBeCalledWith('test-scales', 'test-point');
.toBeCalledWith(true, 'test-scales', 'test-point');
});
});
19 changes: 11 additions & 8 deletions packages/dx-chart-core/src/plugins/animation/computeds.ts
Expand Up @@ -21,9 +21,10 @@ const addKeyframe = (name: string, def: string): void => {
}
};

const getAreaAnimationName = () => {
const getAreaAnimationName = (rotated: boolean) => {
const name = 'animation_transform';
addKeyframe(name, '{ from { transform: scaleY(0); } }');
const attr = rotated ? 'scaleX' : 'scaleY';
addKeyframe(name, `{ from { transform: ${attr}(0); } }`);
return name;
};

Expand All @@ -46,19 +47,21 @@ const getDefaultPieAnimationOptions = ({ index }: Point) => `${0.7 + index * 0.1
const getDefaultScatterAnimationOptions = () => '1.6s';

/** @internal */
export const getAreaAnimationStyle: GetAnimationStyleFn = (scales) => {
export const getAreaAnimationStyle: GetAnimationStyleFn = (rotated, { xScale, yScale }) => {
const x = rotated ? xScale.copy().clamp!(true)(0) : 0;
const y = rotated ? 0 : yScale.copy().clamp!(true)(0);
const animationStyle = {
transformOrigin: `0px ${scales.yScale.copy().clamp!(true)(0)}px`,
transformOrigin: `${x}px ${y}px`,
};
const options = getDefaultAreaAnimationOptions();
return {
animation: `${getAreaAnimationName()} ${options}`,
animation: `${getAreaAnimationName(rotated)} ${options}`,
...animationStyle,
};
};

/** @internal */
export const getPieAnimationStyle: GetAnimationStyleFn = (_, point) => {
export const getPieAnimationStyle: GetAnimationStyleFn = (r, s, point) => {
const options = getDefaultPieAnimationOptions(point!);
return {
animation: `${getPieAnimationName()} ${options}`,
Expand All @@ -74,10 +77,10 @@ export const getScatterAnimationStyle: GetAnimationStyleFn = () => {
};

/** @internal */
export const buildAnimatedStyleGetter: BuildAnimatedStyleGetterFn = (
export const buildAnimatedStyleGetter: BuildAnimatedStyleGetterFn = rotated => (
style, getAnimationStyle, scales, point,
) => {
const animationStyle = getAnimationStyle(scales, point);
const animationStyle = getAnimationStyle(rotated, scales, point);
return {
...animationStyle,
...style,
Expand Down
86 changes: 66 additions & 20 deletions packages/dx-chart-core/src/plugins/axis/computeds.test.ts
@@ -1,10 +1,45 @@
import { isHorizontal } from '../../utils/scale';
import { axisCoordinates, getGridCoordinates, createTickFilter } from './computeds';
import {
axisCoordinates, getGridCoordinates, createTickFilter, getRotatedPosition, isValidPosition,
} from './computeds';

jest.mock('../../utils/scale', () => ({
isHorizontal: jest.fn(),
}));

describe('getRotatedPosition', () => {
it('should return rotated position', () => {
expect(getRotatedPosition('left')).toEqual('bottom');
expect(getRotatedPosition('right')).toEqual('top');
expect(getRotatedPosition('top')).toEqual('right');
expect(getRotatedPosition('bottom')).toEqual('left');
});
});

describe('isValidPosition', () => {
afterEach(jest.clearAllMocks);

it('should check horizontal case', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
expect(isValidPosition('left', 'scale-1', false)).toEqual(false);
expect(isValidPosition('bottom', 'scale-2', true)).toEqual(true);
expect((isHorizontal as jest.Mock).mock.calls).toEqual([
['scale-1', false],
['scale-2', true],
]);
});

it('should check vertical case', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
expect(isValidPosition('right', 'scale-1', false)).toEqual(true);
expect(isValidPosition('top', 'scale-2', true)).toEqual(false);
expect((isHorizontal as jest.Mock).mock.calls).toEqual([
['scale-1', false],
['scale-2', true],
]);
});
});

describe('axisCoordinates', () => {
const tickSize = 5;
const indentFromAxis = 10;
Expand All @@ -20,7 +55,7 @@ describe('axisCoordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, scaleName: 'test-name', position: 'top',
paneSize: [80, 0],
paneSize: [80, 0], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [1, 0],
Expand All @@ -34,15 +69,15 @@ describe('axisCoordinates', () => {
y1: 0, y2: -5, x1: 25, x2: 25,
}],
});
expect(isHorizontal).toBeCalledWith('test-name');
expect(isHorizontal).toBeCalledWith('test-name', false);
expect(scale.ticks).toBeCalledWith(5);
});

it('should return ticks coordinates with horizontal-bottom position', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, scaleName: 'test-name', position: 'bottom',
paneSize: [80, 0],
paneSize: [80, 0], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [1, 0],
Expand All @@ -56,27 +91,27 @@ describe('axisCoordinates', () => {
y1: 0, y2: 5, x1: 25, x2: 25,
}],
});
expect(isHorizontal).toBeCalledWith('test-name');
expect(isHorizontal).toBeCalledWith('test-name', false);
expect(scale.ticks).toBeCalledWith(5);
});

it('should pass correct domain to scale', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
axisCoordinates({
scale, tickSize, indentFromAxis, scaleName: 'test-name', position: 'top',
paneSize: [80, 0],
paneSize: [80, 0], rotated: false,
} as any);
expect(scale.mock.calls).toEqual([
[1],
]);
expect(isHorizontal).toBeCalledWith('test-name');
expect(isHorizontal).toBeCalledWith('test-name', false);
});

it('should return ticks coordinates with vertical-left position', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, scaleName: 'test-name', position: 'left',
paneSize: [0, 80],
paneSize: [0, 80], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [0, 1],
Expand All @@ -90,15 +125,15 @@ describe('axisCoordinates', () => {
textAnchor: 'end',
}],
});
expect(isHorizontal).toBeCalledWith('test-name');
expect(isHorizontal).toBeCalledWith('test-name', false);
expect(scale.ticks).toBeCalledWith(5);
});

it('should return ticks coordinates with vertical-right position', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
const coordinates = axisCoordinates({
tickSize, indentFromAxis, scale, scaleName: 'test-name', position: 'right',
paneSize: [0, 80],
paneSize: [0, 80], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [0, 1],
Expand All @@ -112,15 +147,15 @@ describe('axisCoordinates', () => {
textAnchor: 'start',
}],
});
expect(isHorizontal).toBeCalledWith('test-name');
expect(isHorizontal).toBeCalledWith('test-name', false);
expect(scale.ticks).toBeCalledWith(5);
});

it('should generate ticks when pane size is zero', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
axisCoordinates({
scale, tickSize, indentFromAxis, scaleName: 'test-name', position: 'top',
paneSize: [0, 0],
paneSize: [0, 0], rotated: false,
} as any);
expect(scale.ticks).toBeCalledWith(10);
});
Expand All @@ -131,7 +166,7 @@ describe('axisCoordinates', () => {
try {
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, position: 'top', scaleName: 'test-name',
paneSize: [],
paneSize: [], rotated: false,
} as any);
expect(coordinates.ticks).toEqual([{
key: '0',
Expand Down Expand Up @@ -161,6 +196,7 @@ describe('axisCoordinates', () => {
position: 'top',
scaleName: 'test-name',
paneSize: [],
rotated: false,
});
expect(coordinates.ticks).toEqual([{
key: '0',
Expand All @@ -187,7 +223,7 @@ describe('axisCoordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
axisCoordinates({
scale, tickSize, indentFromAxis, position: 'left', scaleName: 'test-name',
paneSize: [0, 80],
paneSize: [0, 80], rotated: false,
} as any);
expect(scale.mock.calls).toEqual([
['a'],
Expand All @@ -198,7 +234,7 @@ describe('axisCoordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, position: 'bottom', scaleName: 'test-name',
paneSize: [80, 0],
paneSize: [80, 0], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [1, 0],
Expand All @@ -218,7 +254,7 @@ describe('axisCoordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, position: 'top', scaleName: 'test-name',
paneSize: [80, 0],
paneSize: [80, 0], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [1, 0],
Expand All @@ -238,7 +274,7 @@ describe('axisCoordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, position: 'left', scaleName: 'test-name',
paneSize: [0, 80],
paneSize: [0, 80], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [0, 1],
Expand All @@ -258,7 +294,7 @@ describe('axisCoordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
const coordinates = axisCoordinates({
scale, tickSize, indentFromAxis, position: 'right', scaleName: 'test-name',
paneSize: [0, 80],
paneSize: [0, 80], rotated: false,
} as any);
expect(coordinates).toEqual({
sides: [0, 1],
Expand All @@ -283,7 +319,12 @@ describe('getGridCoordinates', () => {

it('should return horizontal coordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(true);
expect(getGridCoordinates({ scale, scaleName: 'test-name', paneSize: [80, 0] })).toEqual([
expect(getGridCoordinates({
scale,
scaleName: 'test-name',
paneSize: [80, 0],
rotated: false,
})).toEqual([
{
key: '0', x: 26, y: 0, dx: 0, dy: 1,
},
Expand All @@ -302,7 +343,12 @@ describe('getGridCoordinates', () => {

it('should return vertical coordinates', () => {
(isHorizontal as jest.Mock).mockReturnValue(false);
expect(getGridCoordinates({ scale, scaleName: 'test-name', paneSize: [0, 80] })).toEqual([
expect(getGridCoordinates({
scale,
scaleName: 'test-name',
paneSize: [0, 80],
rotated: false,
})).toEqual([
{
key: '0', x: 0, y: 26, dx: 1, dy: 0,
},
Expand Down

0 comments on commit def0662

Please sign in to comment.