Skip to content

Commit 6f56ed9

Browse files
authored
feat: add functions for parsing scales (apache-superset#207)
* feat: add more util functions * feat: add unit test * feat: define HasToString * fix: unit test * fix: update unit tests * feat: add scale types * feat: update scale parsing * fix: enum * feat: add color scale extraction * refactor: create scale from config * feat: parse more scales and add more test * feat: add tests for band and point * test: add more unit tests * refactor: separate applyXXX into multiple files * feat: parse nice time * test: add unit tests * test: make 100% coverage * fix: complete coverage * refactor: update type definitions * fix: address comments * fix: add comments for date parts * fix: build issue * fix: broken tests
1 parent 8baede4 commit 6f56ed9

30 files changed

+1648
-19
lines changed

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
],
4141
"license": "Apache-2.0",
4242
"devDependencies": {
43-
"@superset-ui/build-config": "^0.1.1",
43+
"@superset-ui/build-config": "^0.1.3",
4444
"@superset-ui/commit-config": "^0.0.9",
4545
"fast-glob": "^3.0.1",
4646
"fs-extra": "^8.0.1",
@@ -90,6 +90,11 @@
9090
]
9191
},
9292
"typescript": {
93+
"compilerOptions": {
94+
"typeRoots": [
95+
"../../node_modules/vega-lite/typings"
96+
]
97+
},
9398
"include": [
9499
"./storybook/**/*"
95100
]

packages/superset-ui-dimension/src/svg/updateTextNode.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ export default function updateTextNode(
3030
textNode.setAttribute('class', className || '');
3131
}
3232

33-
// clear style
34-
STYLE_FIELDS.forEach((field: keyof TextStyle) => {
35-
textNode.style[field] = null;
36-
});
33+
// Clear style
34+
// Note: multi-word property names are hyphenated and not camel-cased.
35+
textNode.style.removeProperty('font');
36+
textNode.style.removeProperty('font-weight');
37+
textNode.style.removeProperty('font-style');
38+
textNode.style.removeProperty('font-size');
39+
textNode.style.removeProperty('font-family');
40+
textNode.style.removeProperty('letter-spacing');
3741

38-
// apply new style
39-
// Note that the font field will auto-populate other font fields when applicable.
42+
// Apply new style
43+
// Note: the font field will auto-populate other font fields when applicable.
4044
STYLE_FIELDS.filter(
4145
(field: keyof TextStyle) => typeof style[field] !== 'undefined' && style[field] !== null,
4246
).forEach((field: keyof TextStyle) => {

packages/superset-ui-encodeable/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,18 @@
2828
"private": true,
2929
"dependencies": {
3030
"lodash": "^4.17.15",
31+
"@types/d3-scale": "^2.1.1",
32+
"@types/d3-interpolate": "^1.3.1",
33+
"@types/d3-time": "^1.0.10",
34+
"d3-scale": "^3.0.1",
35+
"d3-interpolate": "^1.3.2",
36+
"d3-time": "^1.0.11",
3137
"vega": "^5.4.0",
38+
"vega-expression": "^2.6.0",
3239
"vega-lite": "^3.4.0"
3340
},
3441
"peerDependencies": {
42+
"@superset-ui/color": "^0.12.0",
3543
"@superset-ui/number-format": "^0.12.0",
3644
"@superset-ui/time-format": "^0.12.0"
3745
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { parse, codegen } from 'vega-expression';
2+
import { dateTimeExpr } from 'vega-lite/build/src/datetime';
3+
import { DateTime } from '../types/VegaLite';
4+
5+
export default function parseDateTime(dateTime: string | number | DateTime) {
6+
if (typeof dateTime === 'number' || typeof dateTime === 'string') {
7+
return new Date(dateTime);
8+
}
9+
10+
const expression = dateTimeExpr(dateTime, true) as string;
11+
const code = codegen({ globalvar: 'window' })(parse(expression)).code as string;
12+
// Technically the "code" here is safe to eval(),
13+
// but we will use more conservative approach and manually parse at the moment.
14+
const isUtc = code.startsWith('Date.UTC');
15+
16+
const dateParts = code
17+
.replace(/^(Date[.]UTC|new[ ]Date)\(/, '')
18+
.replace(/\)$/, '')
19+
.split(',')
20+
.map((chunk: string) => Number(chunk.trim())) as [
21+
number, // year
22+
number, // month
23+
number, // date
24+
number, // hours
25+
number, // minutes
26+
number, // seconds
27+
number, // milliseconds
28+
];
29+
30+
return isUtc ? new Date(Date.UTC(...dateParts)) : new Date(...dateParts);
31+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Value } from '../../types/VegaLite';
2+
import { ScaleConfig, D3Scale } from '../../types/Scale';
3+
4+
export default function applyAlign<Output extends Value>(
5+
config: ScaleConfig<Output>,
6+
scale: D3Scale<Output>,
7+
) {
8+
if ('align' in config && typeof config.align !== 'undefined' && 'align' in scale) {
9+
scale.align(config.align);
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Value } from '../../types/VegaLite';
2+
import { ScaleConfig, D3Scale } from '../../types/Scale';
3+
4+
export default function applyClamp<Output extends Value>(
5+
config: ScaleConfig<Output>,
6+
scale: D3Scale<Output>,
7+
) {
8+
if ('clamp' in config && typeof config.clamp !== 'undefined' && 'clamp' in scale) {
9+
scale.clamp(config.clamp);
10+
}
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Value } from '../../types/VegaLite';
2+
import { ScaleConfig, D3Scale, TimeScaleConfig } from '../../types/Scale';
3+
import parseDateTime from '../parseDateTime';
4+
import inferElementTypeFromUnionOfArrayTypes from '../../utils/inferElementTypeFromUnionOfArrayTypes';
5+
import { isTimeScale } from '../../typeGuards/Scale';
6+
7+
export default function applyDomain<Output extends Value>(
8+
config: ScaleConfig<Output>,
9+
scale: D3Scale<Output>,
10+
) {
11+
const { domain, reverse, type } = config;
12+
if (typeof domain !== 'undefined') {
13+
const processedDomain = reverse ? domain.slice().reverse() : domain;
14+
if (isTimeScale(scale, type)) {
15+
const timeDomain = processedDomain as TimeScaleConfig['domain'];
16+
scale.domain(inferElementTypeFromUnionOfArrayTypes(timeDomain).map(d => parseDateTime(d)));
17+
} else {
18+
scale.domain(processedDomain);
19+
}
20+
}
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Value } from '../../types/VegaLite';
2+
import { ScaleConfig, D3Scale } from '../../types/Scale';
3+
4+
export default function applyInterpolate<Output extends Value>(
5+
config: ScaleConfig<Output>,
6+
scale: D3Scale<Output>,
7+
) {
8+
if (
9+
'interpolate' in config &&
10+
typeof config.interpolate !== 'undefined' &&
11+
'interpolate' in scale
12+
) {
13+
// TODO: Need to convert interpolate string into interpolate function
14+
throw new Error('"scale.interpolate" is not supported yet.');
15+
}
16+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {
2+
timeSecond,
3+
timeMinute,
4+
timeHour,
5+
timeDay,
6+
timeYear,
7+
timeMonth,
8+
timeWeek,
9+
utcSecond,
10+
utcMinute,
11+
utcHour,
12+
utcDay,
13+
utcWeek,
14+
utcMonth,
15+
utcYear,
16+
CountableTimeInterval,
17+
} from 'd3-time';
18+
import { ScaleTime } from 'd3-scale';
19+
import { Value, ScaleType, NiceTime } from '../../types/VegaLite';
20+
import { ScaleConfig, D3Scale } from '../../types/Scale';
21+
22+
const localTimeIntervals: {
23+
[key in NiceTime]: CountableTimeInterval;
24+
} = {
25+
day: timeDay,
26+
hour: timeHour,
27+
minute: timeMinute,
28+
month: timeMonth,
29+
second: timeSecond,
30+
week: timeWeek,
31+
year: timeYear,
32+
};
33+
34+
const utcIntervals: {
35+
[key in NiceTime]: CountableTimeInterval;
36+
} = {
37+
day: utcDay,
38+
hour: utcHour,
39+
minute: utcMinute,
40+
month: utcMonth,
41+
second: utcSecond,
42+
week: utcWeek,
43+
year: utcYear,
44+
};
45+
46+
// eslint-disable-next-line complexity
47+
export default function applyNice<Output extends Value>(
48+
config: ScaleConfig<Output>,
49+
scale: D3Scale<Output>,
50+
) {
51+
if ('nice' in config && typeof config.nice !== 'undefined' && 'nice' in scale) {
52+
const { nice } = config;
53+
if (typeof nice === 'boolean') {
54+
if (nice === true) {
55+
scale.nice();
56+
}
57+
} else if (typeof nice === 'number') {
58+
scale.nice(nice);
59+
} else {
60+
const timeScale = scale as ScaleTime<Output, Output>;
61+
const { type } = config;
62+
if (typeof nice === 'string') {
63+
timeScale.nice(type === ScaleType.UTC ? utcIntervals[nice] : localTimeIntervals[nice]);
64+
} else {
65+
const { interval, step } = nice;
66+
const parsedInterval = (type === ScaleType.UTC
67+
? utcIntervals[interval]
68+
: localTimeIntervals[interval]
69+
).every(step);
70+
71+
if (parsedInterval !== null) {
72+
timeScale.nice(parsedInterval as CountableTimeInterval);
73+
}
74+
}
75+
}
76+
}
77+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Value } from '../../types/VegaLite';
2+
import { ScaleConfig, D3Scale } from '../../types/Scale';
3+
4+
export default function applyPadding<Output extends Value>(
5+
config: ScaleConfig<Output>,
6+
scale: D3Scale<Output>,
7+
) {
8+
if ('padding' in config && typeof config.padding !== 'undefined' && 'padding' in scale) {
9+
scale.padding(config.padding);
10+
}
11+
12+
if (
13+
'paddingInner' in config &&
14+
typeof config.paddingInner !== 'undefined' &&
15+
'paddingInner' in scale
16+
) {
17+
scale.paddingInner(config.paddingInner);
18+
}
19+
20+
if (
21+
'paddingOuter' in config &&
22+
typeof config.paddingOuter !== 'undefined' &&
23+
'paddingOuter' in scale
24+
) {
25+
scale.paddingOuter(config.paddingOuter);
26+
}
27+
}

0 commit comments

Comments
 (0)