Skip to content

Commit 27f14a0

Browse files
feat: add minInterval option for custom xDomain (#240)
1 parent d199453 commit 27f14a0

File tree

5 files changed

+3681
-11
lines changed

5 files changed

+3681
-11
lines changed

src/lib/series/domains/x_domain.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,4 +642,41 @@ describe('X Domain', () => {
642642
const errorMessage = 'xDomain for ordinal scale should be an array of values, not a DomainRange object';
643643
expect(attemptToMerge).toThrowError(errorMessage);
644644
});
645+
646+
describe('should account for custom minInterval', () => {
647+
const xValues = new Set([1, 2, 3, 4, 5]);
648+
const specs: Pick<BasicSeriesSpec, 'seriesType' | 'xScaleType'>[] = [
649+
{ seriesType: 'bar', xScaleType: ScaleType.Linear },
650+
];
651+
652+
test('with valid minInterval', () => {
653+
const xDomain = { minInterval: 0.5 };
654+
const mergedDomain = mergeXDomain(specs, xValues, xDomain);
655+
expect(mergedDomain.minInterval).toEqual(0.5);
656+
});
657+
658+
test('with valid minInterval greater than computed minInterval for single datum set', () => {
659+
const xDomain = { minInterval: 10 };
660+
const mergedDomain = mergeXDomain(specs, new Set([5]), xDomain);
661+
expect(mergedDomain.minInterval).toEqual(10);
662+
});
663+
664+
test('with invalid minInterval greater than computed minInterval for multi data set', () => {
665+
const invalidXDomain = { minInterval: 10 };
666+
const attemptToMerge = () => {
667+
mergeXDomain(specs, xValues, invalidXDomain);
668+
};
669+
const expectedError = 'custom xDomain is invalid, custom minInterval is greater than computed minInterval';
670+
expect(attemptToMerge).toThrowError(expectedError);
671+
});
672+
673+
test('with invalid minInterval less than 0', () => {
674+
const invalidXDomain = { minInterval: -1 };
675+
const attemptToMerge = () => {
676+
mergeXDomain(specs, xValues, invalidXDomain);
677+
};
678+
const expectedError = 'custom xDomain is invalid, custom minInterval is less than 0';
679+
expect(attemptToMerge).toThrowError(expectedError);
680+
});
681+
});
645682
});

src/lib/series/domains/x_domain.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export function mergeXDomain(
2929
const values = [...xValues.values()];
3030
let seriesXComputedDomains;
3131
let minInterval = 0;
32+
3233
if (mainXScaleType.scaleType === ScaleType.Ordinal) {
3334
seriesXComputedDomains = computeOrdinalDataDomain(values, identity, false, true);
3435
if (xDomain) {
@@ -40,8 +41,16 @@ export function mergeXDomain(
4041
}
4142
} else {
4243
seriesXComputedDomains = computeContinuousDataDomain(values, identity, true);
44+
let customMinInterval: undefined | number;
45+
4346
if (xDomain) {
44-
if (!Array.isArray(xDomain)) {
47+
if (Array.isArray(xDomain)) {
48+
throw new Error('xDomain for continuous scale should be a DomainRange object, not an array');
49+
}
50+
51+
customMinInterval = xDomain.minInterval;
52+
53+
if (xDomain) {
4554
const [computedDomainMin, computedDomainMax] = seriesXComputedDomains;
4655

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

6473
seriesXComputedDomains = [computedDomainMin, xDomain.max];
6574
}
66-
} else {
67-
throw new Error('xDomain for continuous scale should be a DomainRange object, not an array');
6875
}
6976
}
70-
minInterval = findMinInterval(values);
77+
78+
const computedMinInterval = findMinInterval(values);
79+
if (customMinInterval != null) {
80+
// Allow greater custom min iff xValues has 1 member.
81+
if (xValues.size > 1 && customMinInterval > computedMinInterval) {
82+
throw new Error('custom xDomain is invalid, custom minInterval is greater than computed minInterval');
83+
}
84+
if (customMinInterval < 0) {
85+
throw new Error('custom xDomain is invalid, custom minInterval is less than 0');
86+
}
87+
}
88+
89+
minInterval = customMinInterval || computedMinInterval;
7190
}
7291

7392
return {

src/lib/series/specs.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,34 @@ export type Datum = any;
1717
export type Rotation = 0 | 90 | -90 | 180;
1818
export type Rendering = 'canvas' | 'svg';
1919

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

24-
export interface UpperBoundedDomain {
25-
max: number;
32+
interface LowerBound {
33+
/** Lower bound of domain range */
34+
min: number;
2635
}
2736

28-
export interface CompleteBoundedDomain {
29-
min: number;
37+
interface UpperBound {
38+
/** Upper bound of domain range */
3039
max: number;
3140
}
3241

33-
export type DomainRange = LowerBoundedDomain | UpperBoundedDomain | CompleteBoundedDomain;
42+
export type LowerBoundedDomain = DomainMinInterval & LowerBound;
43+
export type UpperBoundedDomain = DomainMinInterval & UpperBound;
44+
export type CompleteBoundedDomain = DomainMinInterval & LowerBound & UpperBound;
45+
export type UnboundedDomainWithInterval = DomainMinInterval;
46+
47+
export type DomainRange = LowerBoundedDomain | UpperBoundedDomain | CompleteBoundedDomain | UnboundedDomainWithInterval;
3448

3549
export interface DisplayValueSpec {
3650
/** Show value label in chart element */

0 commit comments

Comments
 (0)