Skip to content

Commit

Permalink
feat: add minInterval option for custom xDomain (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmacunningham authored Jun 19, 2019
1 parent d199453 commit 27f14a0
Show file tree
Hide file tree
Showing 5 changed files with 3,681 additions and 11 deletions.
37 changes: 37 additions & 0 deletions src/lib/series/domains/x_domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,41 @@ describe('X Domain', () => {
const errorMessage = 'xDomain for ordinal scale should be an array of values, not a DomainRange object';
expect(attemptToMerge).toThrowError(errorMessage);
});

describe('should account for custom minInterval', () => {
const xValues = new Set([1, 2, 3, 4, 5]);
const specs: Pick<BasicSeriesSpec, 'seriesType' | 'xScaleType'>[] = [
{ seriesType: 'bar', xScaleType: ScaleType.Linear },
];

test('with valid minInterval', () => {
const xDomain = { minInterval: 0.5 };
const mergedDomain = mergeXDomain(specs, xValues, xDomain);
expect(mergedDomain.minInterval).toEqual(0.5);
});

test('with valid minInterval greater than computed minInterval for single datum set', () => {
const xDomain = { minInterval: 10 };
const mergedDomain = mergeXDomain(specs, new Set([5]), xDomain);
expect(mergedDomain.minInterval).toEqual(10);
});

test('with invalid minInterval greater than computed minInterval for multi data set', () => {
const invalidXDomain = { minInterval: 10 };
const attemptToMerge = () => {
mergeXDomain(specs, xValues, invalidXDomain);
};
const expectedError = 'custom xDomain is invalid, custom minInterval is greater than computed minInterval';
expect(attemptToMerge).toThrowError(expectedError);
});

test('with invalid minInterval less than 0', () => {
const invalidXDomain = { minInterval: -1 };
const attemptToMerge = () => {
mergeXDomain(specs, xValues, invalidXDomain);
};
const expectedError = 'custom xDomain is invalid, custom minInterval is less than 0';
expect(attemptToMerge).toThrowError(expectedError);
});
});
});
27 changes: 23 additions & 4 deletions src/lib/series/domains/x_domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function mergeXDomain(
const values = [...xValues.values()];
let seriesXComputedDomains;
let minInterval = 0;

if (mainXScaleType.scaleType === ScaleType.Ordinal) {
seriesXComputedDomains = computeOrdinalDataDomain(values, identity, false, true);
if (xDomain) {
Expand All @@ -40,8 +41,16 @@ export function mergeXDomain(
}
} else {
seriesXComputedDomains = computeContinuousDataDomain(values, identity, true);
let customMinInterval: undefined | number;

if (xDomain) {
if (!Array.isArray(xDomain)) {
if (Array.isArray(xDomain)) {
throw new Error('xDomain for continuous scale should be a DomainRange object, not an array');
}

customMinInterval = xDomain.minInterval;

if (xDomain) {
const [computedDomainMin, computedDomainMax] = seriesXComputedDomains;

if (isCompleteBound(xDomain)) {
Expand All @@ -63,11 +72,21 @@ export function mergeXDomain(

seriesXComputedDomains = [computedDomainMin, xDomain.max];
}
} else {
throw new Error('xDomain for continuous scale should be a DomainRange object, not an array');
}
}
minInterval = findMinInterval(values);

const computedMinInterval = findMinInterval(values);
if (customMinInterval != null) {
// Allow greater custom min iff xValues has 1 member.
if (xValues.size > 1 && customMinInterval > computedMinInterval) {
throw new Error('custom xDomain is invalid, custom minInterval is greater than computed minInterval');
}
if (customMinInterval < 0) {
throw new Error('custom xDomain is invalid, custom minInterval is less than 0');
}
}

minInterval = customMinInterval || computedMinInterval;
}

return {
Expand Down
28 changes: 21 additions & 7 deletions src/lib/series/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,34 @@ export type Datum = any;
export type Rotation = 0 | 90 | -90 | 180;
export type Rendering = 'canvas' | 'svg';

export interface LowerBoundedDomain {
min: number;
interface DomainMinInterval {
/** Custom minInterval for the domain which will affect data bucket size.
* The minInterval cannot be greater than the computed minimum interval between any two adjacent data points.
* Further, if you specify a custom numeric minInterval for a timeseries, please note that due to the restriction
* above, the specified numeric minInterval will be interpreted as a fixed interval.
* This means that, for example, if you have yearly timeseries data that ranges from 2016 to 2019 and you manually
* compute the interval between 2016 and 2017, you'll have 366 days due to 2016 being a leap year. This will not
* be a valid interval because it is greater than the computed minInterval of 365 days betwen the other years.
*/
minInterval?: number;
}

export interface UpperBoundedDomain {
max: number;
interface LowerBound {
/** Lower bound of domain range */
min: number;
}

export interface CompleteBoundedDomain {
min: number;
interface UpperBound {
/** Upper bound of domain range */
max: number;
}

export type DomainRange = LowerBoundedDomain | UpperBoundedDomain | CompleteBoundedDomain;
export type LowerBoundedDomain = DomainMinInterval & LowerBound;
export type UpperBoundedDomain = DomainMinInterval & UpperBound;
export type CompleteBoundedDomain = DomainMinInterval & LowerBound & UpperBound;
export type UnboundedDomainWithInterval = DomainMinInterval;

export type DomainRange = LowerBoundedDomain | UpperBoundedDomain | CompleteBoundedDomain | UnboundedDomainWithInterval;

export interface DisplayValueSpec {
/** Show value label in chart element */
Expand Down
Loading

0 comments on commit 27f14a0

Please sign in to comment.