Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(domain): scale data to a specific domain via axis spec #98

Merged
merged 24 commits into from
Mar 19, 2019
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b0db741
refactor(domain): limit domain range with DomainRange interface
emmacunningham Mar 11, 2019
c00fc0e
test(y_domain): add test for coerceYScaleTypes when no specs
emmacunningham Mar 12, 2019
cba5cf1
test(y_domain): add test for mismatch spec case
emmacunningham Mar 12, 2019
f48e00a
refactor(domain): add domain range prop to axis spec
emmacunningham Mar 12, 2019
4474a76
feat(y_domain): limit y domain using axis specs
emmacunningham Mar 13, 2019
c708e0c
refactor(axis_utils): remove unused functions and add test coverage
emmacunningham Mar 13, 2019
208b3b3
test(y_domain): add test for domain limiting
emmacunningham Mar 13, 2019
eb91b57
feat(x_domain): add custom x_domain via settings spec
emmacunningham Mar 14, 2019
a19b1aa
refactor(y_domain): define only y axis domain on axis spec
emmacunningham Mar 14, 2019
72b1b1c
test(axis_utils): add test for domain validation
emmacunningham Mar 14, 2019
cff4d89
refactor(axis_utils): move xDomain check
emmacunningham Mar 14, 2019
5b43570
test(x_domain): add tests for custom domain
emmacunningham Mar 15, 2019
8702f0f
docs(axis): add knobs for domain configurations
emmacunningham Mar 15, 2019
9d73e1f
feat(axis): implement axis hide feature
emmacunningham Mar 15, 2019
b9e828b
docs(axis): add hide knob to custom domain story
emmacunningham Mar 15, 2019
20e8e99
fix(rendering): exclude bar data which is not within ordinal x domain
emmacunningham Mar 18, 2019
ca86193
style: remove addressed TODOs
emmacunningham Mar 18, 2019
e7bf8c4
style: remove superfluous extra line
emmacunningham Mar 18, 2019
faf9f25
feat(axis_utils): throw when adding xDomain via axis spec
emmacunningham Mar 19, 2019
6b36bbc
feat(x_domain): throw when custom domain is invalid
emmacunningham Mar 19, 2019
b49d768
test(axis_utils): use CanvasTextBboxCalculator instead of SVG
emmacunningham Mar 19, 2019
9c55397
refactor(axis_utils): return early if no domain
emmacunningham Mar 19, 2019
b3c5078
refactor(x_domain): remove explicit undefineds from tests
emmacunningham Mar 19, 2019
5516d55
refactor(utils): make domain parameter optional in computeSeriesDomain
emmacunningham Mar 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 111 additions & 13 deletions src/lib/axes/axis_utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { XDomain } from '../series/domains/x_domain';
import { YDomain } from '../series/domains/y_domain';
import { Position } from '../series/specs';
import { AxisSpec, DomainRange, Position } from '../series/specs';
import { LIGHT_THEME } from '../themes/light_theme';
import { getAxisId, getGroupId } from '../utils/ids';
import { getAxisId, getGroupId, GroupId } from '../utils/ids';
import { ScaleType } from '../utils/scales/scales';
import {
centerRotationOrigin,
Expand All @@ -14,15 +14,17 @@ import {
getAxisTicksPositions,
getHorizontalAxisGridLineProps,
getHorizontalAxisTickLineProps,
getHorizontalDomain,
getMaxBboxDimensions,
getMinMaxRange,
getScaleForAxisSpec,
getTickLabelProps,
getVerticalAxisGridLineProps,
getVerticalAxisTickLineProps,
getVerticalDomain,
getVisibleTicks,
isHorizontal,
isVertical,
isYDomain,
mergeDomainsByGroupId,
} from './axis_utils';
import { CanvasTextBBoxCalculator } from './canvas_text_bbox_calculator';
import { SvgTextBBoxCalculator } from './svg_text_bbox_calculator';
Expand Down Expand Up @@ -71,7 +73,7 @@ describe('Axis computational utils', () => {
maxLabelTextWidth: 10,
maxLabelTextHeight: 10,
};
const verticalAxisSpec = {
const verticalAxisSpec: AxisSpec = {
id: getAxisId('axis_1'),
groupId: getGroupId('group_1'),
hide: false,
Expand All @@ -86,7 +88,7 @@ describe('Axis computational utils', () => {
showGridLines: true,
};

const horizontalAxisSpec = {
const horizontalAxisSpec: AxisSpec = {
id: getAxisId('axis_2'),
groupId: getGroupId('group_1'),
hide: false,
Expand Down Expand Up @@ -149,6 +151,21 @@ describe('Axis computational utils', () => {
bboxCalculator.destroy();
});

test('should not compute axis dimensions when spec is configured to hide', () => {
const bboxCalculator = new SvgTextBBoxCalculator();
markov00 marked this conversation as resolved.
Show resolved Hide resolved
verticalAxisSpec.hide = true;
const axisDimensions = computeAxisTicksDimensions(
verticalAxisSpec,
xDomain,
[yDomain],
1,
bboxCalculator,
0,
axes,
);
expect(axisDimensions).toBe(null);
});

test('should compute dimensions for the bounding box containing a rotated label', () => {
expect(computeRotatedLabelDimensions({ width: 1, height: 2 }, 0)).toEqual({
width: 1,
Expand Down Expand Up @@ -914,13 +931,94 @@ describe('Axis computational utils', () => {
expect(horizontalAxisGridLines).toEqual([25, 0, 25, 100]);
});

test('should return correct domain based on rotation', () => {
const chartRotation = 180;
expect(getHorizontalDomain(xDomain, [yDomain], chartRotation)).toEqual(xDomain);
expect(getVerticalDomain(xDomain, [yDomain], chartRotation)).toEqual([yDomain]);
test('should determine orientation of axis position', () => {
expect(isVertical(Position.Left)).toBe(true);
expect(isVertical(Position.Right)).toBe(true);
expect(isVertical(Position.Top)).toBe(false);
expect(isVertical(Position.Bottom)).toBe(false);

expect(isHorizontal(Position.Left)).toBe(false);
expect(isHorizontal(Position.Right)).toBe(false);
expect(isHorizontal(Position.Top)).toBe(true);
expect(isHorizontal(Position.Bottom)).toBe(true);
});

test('should determine if axis belongs to yDomain', () => {
const verticalY = isYDomain(Position.Left, 0);
expect(verticalY).toBe(true);

const verticalX = isYDomain(Position.Left, 90);
expect(verticalX).toBe(false);

const horizontalX = isYDomain(Position.Top, 0);
expect(horizontalX).toBe(false);

const horizontalY = isYDomain(Position.Top, 90);
expect(horizontalY).toBe(true);
});

test('should merge axis domains by group id', () => {
const groupId = getGroupId('group_1');
const domainRange1 = {
min: 2,
max: 9,
};

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

// Base case
const expectedSimpleMap = new Map<GroupId, DomainRange>();
expectedSimpleMap.set(groupId, { min: 2, max: 9 });

const simpleDomainsByGroupId = mergeDomainsByGroupId(axesSpecs, 0);
expect(simpleDomainsByGroupId).toEqual(expectedSimpleMap);

// Multiple definitions for the same group
const domainRange2 = {
min: 0,
max: 7,
};

const altVerticalAxisSpec = { ...verticalAxisSpec, id: getAxisId('axis2') };

altVerticalAxisSpec.domain = domainRange2;
axesSpecs.set(altVerticalAxisSpec.id, altVerticalAxisSpec);

const expectedMergedMap = new Map<GroupId, DomainRange>();
expectedMergedMap.set(groupId, { min: 0, max: 9 });

const mergedDomainsByGroupId = mergeDomainsByGroupId(axesSpecs, 0);
expect(mergedDomainsByGroupId).toEqual(expectedMergedMap);

// xDomain limit
horizontalAxisSpec.domain = {
min: 5,
max: 15,
};
axesSpecs.set(horizontalAxisSpec.id, horizontalAxisSpec);

const mergedMultiDomainsByGroupId = mergeDomainsByGroupId(axesSpecs, 0);

expect(mergedMultiDomainsByGroupId).toEqual(expectedMergedMap);
});

test('should throw on invalid domain', () => {
const domainRange1 = {
min: 9,
max: 2,
};

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const attemptToMerge = () => { mergeDomainsByGroupId(axesSpecs, 0); };
const expectedError = '[Axis axis_1]: custom domain is invalid, min is greater than max';

const skewChartRotation = 45;
expect(getHorizontalDomain(xDomain, [yDomain], skewChartRotation)).toEqual([yDomain]);
expect(getVerticalDomain(xDomain, [yDomain], skewChartRotation)).toEqual(xDomain);
expect(attemptToMerge).toThrowError(expectedError);
});
});
101 changes: 59 additions & 42 deletions src/lib/axes/axis_utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { XDomain } from '../series/domains/x_domain';
import { YDomain } from '../series/domains/y_domain';
import { computeXScale, computeYScales } from '../series/scales';
import { AxisSpec, Position, Rotation, TickFormatter } from '../series/specs';
import { AxisSpec, DomainRange, Position, Rotation, TickFormatter } from '../series/specs';
import { AxisConfig, Theme } from '../themes/theme';
import { Dimensions, Margins } from '../utils/dimensions';
import { Domain } from '../utils/domain';
import { AxisId } from '../utils/ids';
import { AxisId, GroupId } from '../utils/ids';
import { Scale, ScaleType } from '../utils/scales/scales';
import { BBox, BBoxCalculator } from './bbox_calculator';

Expand Down Expand Up @@ -54,6 +54,10 @@ export function computeAxisTicksDimensions(
chartRotation: Rotation,
axisConfig: AxisConfig,
): AxisTicksDimensions | null {
if (axisSpec.hide) {
return null;
}

const scale = getScaleForAxisSpec(
axisSpec,
xDomain,
Expand All @@ -80,6 +84,16 @@ export function computeAxisTicksDimensions(
...dimensions,
};
}

export function isYDomain(position: Position, chartRotation: Rotation): boolean {
const isStraightRotation = chartRotation === 0 || chartRotation === 180;
if (isVertical(position)) {
return isStraightRotation;
}

return !isStraightRotation;
}

export function getScaleForAxisSpec(
axisSpec: AxisSpec,
xDomain: XDomain,
Expand All @@ -89,9 +103,9 @@ export function getScaleForAxisSpec(
minRange: number,
maxRange: number,
): Scale | null {
const axisDomain = getAxisDomain(axisSpec.position, xDomain, yDomain, chartRotation);
// If axisDomain is an array of values, this is an array of YDomains
if (Array.isArray(axisDomain)) {
const axisIsYDomain = isYDomain(axisSpec.position, chartRotation);

if (axisIsYDomain) {
const yScales = computeYScales(yDomain, minRange, maxRange);
if (yScales.has(axisSpec.groupId)) {
return yScales.get(axisSpec.groupId)!;
Expand Down Expand Up @@ -598,47 +612,50 @@ export function computeAxisGridLinePositions(
return positions;
}

export function getVerticalDomain(
xDomain: XDomain,
yDomain: YDomain[],
chartRotation: number,
): XDomain | YDomain[] {
if (chartRotation === 0 || chartRotation === 180) {
return yDomain;
} else {
return xDomain;
}
}

export function getHorizontalDomain(
xDomain: XDomain,
yDomain: YDomain[],
chartRotation: number,
): XDomain | YDomain[] {
if (chartRotation === 0 || chartRotation === 180) {
return xDomain;
} else {
return yDomain;
}
}

export function getAxisDomain(
position: Position,
xDomain: XDomain,
yDomain: YDomain[],
chartRotation: number,
): XDomain | YDomain[] {
if (!isHorizontal(position)) {
return getVerticalDomain(xDomain, yDomain, chartRotation);
} else {
return getHorizontalDomain(xDomain, yDomain, chartRotation);
}
}

export function isVertical(position: Position) {
return position === Position.Left || position === Position.Right;
}

export function isHorizontal(position: Position) {
return !isVertical(position);
}

export function mergeDomainsByGroupId(
axesSpecs: Map<AxisId, AxisSpec>,
chartRotation: Rotation,
): Map<GroupId, DomainRange> {
const domainsByGroupId = new Map<GroupId, DomainRange>();
axesSpecs.forEach((spec: AxisSpec, id: AxisId) => {
const { groupId, domain } = spec;

if (domain) {
markov00 marked this conversation as resolved.
Show resolved Hide resolved
const isAxisYDomain = isYDomain(spec.position, chartRotation);

if (!isAxisYDomain) {
// tslint:disable-next-line:no-console
console.warn(`[Axis ${id}]: custom domain for xDomain should be defined in Settings`);
emmacunningham marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (domain.min > domain.max) {
const errorMessage = `[Axis ${id}]: custom domain is invalid, min is greater than max`;
throw new Error(errorMessage);
}

const prevGroupDomain = domainsByGroupId.get(groupId);

if (prevGroupDomain) {
const mergedDomain = {
min: Math.min(domain.min, prevGroupDomain.min),
max: Math.max(domain.max, prevGroupDomain.max),
};

domainsByGroupId.set(groupId, mergedDomain);
} else {
domainsByGroupId.set(groupId, domain);
}
}
});

return domainsByGroupId;
}
Loading