@uwdata/mosaic-spec 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +57 -0
- package/dist/mosaic-spec.js +45445 -0
- package/dist/mosaic-spec.min.js +89 -0
- package/package.json +37 -0
- package/src/ast/ASTNode.js +18 -0
- package/src/ast/DataNode.js +226 -0
- package/src/ast/ExpressionNode.js +65 -0
- package/src/ast/HConcatNode.js +29 -0
- package/src/ast/HSpaceNode.js +25 -0
- package/src/ast/InputNode.js +34 -0
- package/src/ast/LiteralNode.js +21 -0
- package/src/ast/OptionsNode.js +52 -0
- package/src/ast/ParamNode.js +55 -0
- package/src/ast/ParamRefNode.js +22 -0
- package/src/ast/PlotAttributeNode.js +55 -0
- package/src/ast/PlotFromNode.js +44 -0
- package/src/ast/PlotInteractorNode.js +34 -0
- package/src/ast/PlotLegendNode.js +36 -0
- package/src/ast/PlotMarkNode.js +75 -0
- package/src/ast/PlotNode.js +66 -0
- package/src/ast/SelectionNode.js +26 -0
- package/src/ast/SpecNode.js +47 -0
- package/src/ast/TransformNode.js +118 -0
- package/src/ast/VConcatNode.js +28 -0
- package/src/ast/VSpaceNode.js +25 -0
- package/src/ast-to-dom.js +64 -0
- package/src/ast-to-esm.js +178 -0
- package/src/config/components.js +28 -0
- package/src/config/extensions.js +23 -0
- package/src/config/inputs.js +12 -0
- package/src/config/plots.js +58 -0
- package/src/config/transforms.js +35 -0
- package/src/constants.js +51 -0
- package/src/index.js +36 -0
- package/src/parse-spec.js +111 -0
- package/src/util.js +47 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { parseHConcat } from '../ast/HConcatNode.js';
|
|
2
|
+
import { parseHSpace } from '../ast/HSpaceNode.js';
|
|
3
|
+
import { parseInput } from '../ast/InputNode.js';
|
|
4
|
+
import { parseLegend } from '../ast/PlotLegendNode.js';
|
|
5
|
+
import { parsePlot, parseTopLevelMark } from '../ast/PlotNode.js';
|
|
6
|
+
import { parseVConcat } from '../ast/VConcatNode.js';
|
|
7
|
+
import { parseVSpace } from '../ast/VSpaceNode.js';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
HCONCAT, HSPACE, INPUT, LEGEND, MARK, PLOT, VCONCAT, VSPACE
|
|
11
|
+
} from '../constants.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Map of specification keys to component parsers.
|
|
15
|
+
*/
|
|
16
|
+
export function componentMap(overrides = []) {
|
|
17
|
+
return new Map([
|
|
18
|
+
[ PLOT, parsePlot ],
|
|
19
|
+
[ MARK, parseTopLevelMark ],
|
|
20
|
+
[ LEGEND, parseLegend ],
|
|
21
|
+
[ INPUT, parseInput ],
|
|
22
|
+
[ HCONCAT, parseHConcat ],
|
|
23
|
+
[ VCONCAT, parseVConcat ],
|
|
24
|
+
[ HSPACE, parseHSpace ],
|
|
25
|
+
[ VSPACE, parseVSpace ],
|
|
26
|
+
...overrides
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SPATIAL_DATA } from '../ast/DataNode.js';
|
|
2
|
+
import { SpecNode } from '../ast/SpecNode.js';
|
|
3
|
+
import { toArray } from '../util.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Construct a set of database extensions to load.
|
|
7
|
+
* Automatically adds the spatial extension if a
|
|
8
|
+
* dataset with format "spatial" is loaded.
|
|
9
|
+
* @param {SpecNode} ast Mosaic AST root node.
|
|
10
|
+
* @returns {Set<string>} A set of extension names.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveExtensions(ast) {
|
|
13
|
+
const spec = ast.config?.extensions;
|
|
14
|
+
const exts = new Set(spec ? toArray(spec) : []);
|
|
15
|
+
|
|
16
|
+
for (const def of Object.values(ast.data)) {
|
|
17
|
+
if (def.format === SPATIAL_DATA) {
|
|
18
|
+
exts.add('spatial');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return exts;
|
|
23
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attributeDirectives,
|
|
3
|
+
interactorDirectives,
|
|
4
|
+
legendDirectives,
|
|
5
|
+
markDirectives
|
|
6
|
+
} from '@uwdata/vgplot';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generate an object of lookup maps for vgplot components.
|
|
10
|
+
*/
|
|
11
|
+
export function plotNames({
|
|
12
|
+
attributes = plotAttributeNames(),
|
|
13
|
+
interactors = plotInteractorNames(),
|
|
14
|
+
legends = plotLegendNames(),
|
|
15
|
+
marks = plotMarkNames()
|
|
16
|
+
} = {}) {
|
|
17
|
+
return { attributes, interactors, legends, marks };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Names of attribute directive functions.
|
|
22
|
+
*/
|
|
23
|
+
export function plotAttributeNames(overrides = []) {
|
|
24
|
+
return new Set([
|
|
25
|
+
...Object.keys(attributeDirectives),
|
|
26
|
+
...overrides
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Names interactor directive functions.
|
|
32
|
+
*/
|
|
33
|
+
export function plotInteractorNames(overrides = []) {
|
|
34
|
+
return new Set([
|
|
35
|
+
...Object.keys(interactorDirectives),
|
|
36
|
+
...overrides
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Names of legend directive functions.
|
|
42
|
+
*/
|
|
43
|
+
export function plotLegendNames(overrides = []) {
|
|
44
|
+
return new Set([
|
|
45
|
+
...Object.keys(legendDirectives),
|
|
46
|
+
...overrides
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Names of mark directive functions.
|
|
52
|
+
*/
|
|
53
|
+
export function plotMarkNames(overrides = []) {
|
|
54
|
+
return new Set([
|
|
55
|
+
...Object.keys(markDirectives),
|
|
56
|
+
...overrides
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid transform method names.
|
|
3
|
+
*/
|
|
4
|
+
export function transformNames(overrides = []) {
|
|
5
|
+
return new Set([
|
|
6
|
+
'avg',
|
|
7
|
+
'bin',
|
|
8
|
+
'centroid',
|
|
9
|
+
'centroidX',
|
|
10
|
+
'centroidY',
|
|
11
|
+
'count',
|
|
12
|
+
'dateMonth',
|
|
13
|
+
'dateMonthDay',
|
|
14
|
+
'dateDay',
|
|
15
|
+
'geojson',
|
|
16
|
+
'max',
|
|
17
|
+
'median',
|
|
18
|
+
'min',
|
|
19
|
+
'mode',
|
|
20
|
+
'quantile',
|
|
21
|
+
'sum',
|
|
22
|
+
'row_number',
|
|
23
|
+
'rank',
|
|
24
|
+
'dense_rank',
|
|
25
|
+
'percent_rank',
|
|
26
|
+
'cume_dist',
|
|
27
|
+
'ntile',
|
|
28
|
+
'lag',
|
|
29
|
+
'lead',
|
|
30
|
+
'first_value',
|
|
31
|
+
'last_value',
|
|
32
|
+
'nth_value',
|
|
33
|
+
...overrides
|
|
34
|
+
]);
|
|
35
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// top-level spec
|
|
2
|
+
export const SPEC = 'spec';
|
|
3
|
+
|
|
4
|
+
// literal values
|
|
5
|
+
export const LITERAL = 'literal';
|
|
6
|
+
|
|
7
|
+
// dataset identifier
|
|
8
|
+
export const DATAREF = 'dataref';
|
|
9
|
+
|
|
10
|
+
// options object
|
|
11
|
+
export const OPTIONS = 'options';
|
|
12
|
+
|
|
13
|
+
// params and selections
|
|
14
|
+
export const SELECTION = 'selection';
|
|
15
|
+
export const PARAMREF = 'paramref';
|
|
16
|
+
export const PARAM = 'param';
|
|
17
|
+
|
|
18
|
+
// param and selection types
|
|
19
|
+
export const SELECT = 'select';
|
|
20
|
+
export const VALUE = 'value';
|
|
21
|
+
export const CROSSFILTER = 'crossfilter';
|
|
22
|
+
export const INTERSECT = 'intersect';
|
|
23
|
+
export const UNION = 'union';
|
|
24
|
+
export const SINGLE = 'single';
|
|
25
|
+
|
|
26
|
+
// data definitions
|
|
27
|
+
export const DATA = 'data';
|
|
28
|
+
|
|
29
|
+
// sql expressions
|
|
30
|
+
export const EXPRESSION = 'expression';
|
|
31
|
+
export const SQL = 'sql';
|
|
32
|
+
export const AGG = 'agg';
|
|
33
|
+
|
|
34
|
+
// inputs
|
|
35
|
+
export const INPUT = 'input';
|
|
36
|
+
|
|
37
|
+
// layout
|
|
38
|
+
export const HCONCAT = 'hconcat';
|
|
39
|
+
export const VCONCAT = 'vconcat';
|
|
40
|
+
export const HSPACE = 'hspace';
|
|
41
|
+
export const VSPACE = 'vspace';
|
|
42
|
+
|
|
43
|
+
// vgplot
|
|
44
|
+
export const MARK = 'mark';
|
|
45
|
+
export const FROM = 'from';
|
|
46
|
+
export const PLOT = 'plot';
|
|
47
|
+
export const LEGEND = 'legend';
|
|
48
|
+
export const ATTRIBUTE = 'attribute';
|
|
49
|
+
export const TRANSFORM = 'transform';
|
|
50
|
+
export const INTERACTOR = 'interactor';
|
|
51
|
+
export const FIXED = 'Fixed';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export { astToDOM, InstantiateContext } from './ast-to-dom.js';
|
|
2
|
+
export { astToESM, CodegenContext } from './ast-to-esm.js';
|
|
3
|
+
export { parseSpec } from './parse-spec.js';
|
|
4
|
+
|
|
5
|
+
export * from './constants.js';
|
|
6
|
+
export { ASTNode } from './ast/ASTNode.js';
|
|
7
|
+
export {
|
|
8
|
+
DataNode,
|
|
9
|
+
QueryDataNode,
|
|
10
|
+
TableDataNode,
|
|
11
|
+
FileDataNode,
|
|
12
|
+
CSVDataNode,
|
|
13
|
+
JSONDataNode,
|
|
14
|
+
ParquetDataNode,
|
|
15
|
+
SpatialDataNode,
|
|
16
|
+
LiteralJSONDataNode
|
|
17
|
+
} from './ast/DataNode.js';
|
|
18
|
+
export { ExpressionNode } from './ast/ExpressionNode.js';
|
|
19
|
+
export { HConcatNode } from './ast/HConcatNode.js';
|
|
20
|
+
export { HSpaceNode } from './ast/HSpaceNode.js';
|
|
21
|
+
export { InputNode } from './ast/InputNode.js';
|
|
22
|
+
export { LiteralNode } from './ast/LiteralNode.js';
|
|
23
|
+
export { OptionsNode } from './ast/OptionsNode.js';
|
|
24
|
+
export { ParamNode } from './ast/ParamNode.js';
|
|
25
|
+
export { ParamRefNode } from './ast/ParamRefNode.js';
|
|
26
|
+
export { PlotAttributeNode, PlotFixedNode } from './ast/PlotAttributeNode.js';
|
|
27
|
+
export { PlotFromNode } from './ast/PlotFromNode.js';
|
|
28
|
+
export { PlotInteractorNode } from './ast/PlotInteractorNode.js';
|
|
29
|
+
export { PlotLegendNode } from './ast/PlotLegendNode.js';
|
|
30
|
+
export { PlotMarkNode } from './ast/PlotMarkNode.js';
|
|
31
|
+
export { PlotNode } from './ast/PlotNode.js';
|
|
32
|
+
export { SelectionNode } from './ast/SelectionNode.js';
|
|
33
|
+
export { SpecNode } from './ast/SpecNode.js';
|
|
34
|
+
export { TransformNode } from './ast/TransformNode.js';
|
|
35
|
+
export { VConcatNode } from './ast/VConcatNode.js';
|
|
36
|
+
export { VSpaceNode } from './ast/VSpaceNode.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { parseData } from './ast/DataNode.js'
|
|
2
|
+
import { LiteralNode } from './ast/LiteralNode.js';
|
|
3
|
+
import { parseParam, ParamNode } from './ast/ParamNode.js';
|
|
4
|
+
import { ParamRefNode } from './ast/ParamRefNode.js';
|
|
5
|
+
import { parseAttribute } from './ast/PlotAttributeNode.js';
|
|
6
|
+
import { SelectionNode } from './ast/SelectionNode.js';
|
|
7
|
+
import { SpecNode } from './ast/SpecNode.js';
|
|
8
|
+
|
|
9
|
+
import { componentMap } from './config/components.js';
|
|
10
|
+
import { inputNames } from './config/inputs.js';
|
|
11
|
+
import { plotNames } from './config/plots.js';
|
|
12
|
+
import { transformNames } from './config/transforms.js';
|
|
13
|
+
|
|
14
|
+
import { error, isString, paramRef } from './util.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse a Mosaic specification to an AST (abstract syntax tree).
|
|
18
|
+
* @param {object|string} spec The input specification as an object
|
|
19
|
+
* or JSON string.
|
|
20
|
+
* @param {object} [options] Optional parse options object.
|
|
21
|
+
* @returns {SpecNode} The top-level AST spec node.
|
|
22
|
+
*/
|
|
23
|
+
export function parseSpec(spec, options) {
|
|
24
|
+
spec = isString(spec) ? JSON.parse(spec) : spec;
|
|
25
|
+
return new ParseContext(options).parse(spec);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class ParseContext {
|
|
29
|
+
constructor({
|
|
30
|
+
components = componentMap(),
|
|
31
|
+
transforms = transformNames(),
|
|
32
|
+
inputs = inputNames(),
|
|
33
|
+
plot = plotNames(),
|
|
34
|
+
params = [],
|
|
35
|
+
datasets = []
|
|
36
|
+
} = {}) {
|
|
37
|
+
this.components = components;
|
|
38
|
+
this.transforms = transforms;
|
|
39
|
+
this.inputs = inputs;
|
|
40
|
+
this.plot = plot;
|
|
41
|
+
this.params = new Map(params);
|
|
42
|
+
this.datasets = new Map(datasets);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
parse(spec) {
|
|
46
|
+
// eslint-disable-next-line no-unused-vars
|
|
47
|
+
const {
|
|
48
|
+
meta,
|
|
49
|
+
config,
|
|
50
|
+
data = {},
|
|
51
|
+
params,
|
|
52
|
+
plotDefaults = {},
|
|
53
|
+
...root
|
|
54
|
+
} = spec;
|
|
55
|
+
|
|
56
|
+
// parse data definitions
|
|
57
|
+
for (const name in data) {
|
|
58
|
+
this.datasets.set(name, parseData(name, data[name], this));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// parse default attributes
|
|
62
|
+
this.plotDefaults = Object.entries(plotDefaults)
|
|
63
|
+
.map(([key, value]) => parseAttribute(key, value, this));
|
|
64
|
+
|
|
65
|
+
// parse param/selection definitions
|
|
66
|
+
for (const name in params) {
|
|
67
|
+
this.params.set(name, parseParam(params[name], this));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new SpecNode(
|
|
71
|
+
this.parseComponent(root),
|
|
72
|
+
meta ? { ...meta } : undefined,
|
|
73
|
+
config ? { ...config } : undefined,
|
|
74
|
+
Object.fromEntries(this.datasets),
|
|
75
|
+
Object.fromEntries(this.params),
|
|
76
|
+
this.plotDefaults
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
parseComponent(spec) {
|
|
81
|
+
for (const [key, parse] of this.components) {
|
|
82
|
+
const value = spec[key];
|
|
83
|
+
if (value != null) {
|
|
84
|
+
return parse(spec, this);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
this.error(`Invalid specification.`, spec);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
maybeParam(value, makeNode = () => new ParamNode) {
|
|
91
|
+
const { params } = this;
|
|
92
|
+
const name = paramRef(value);
|
|
93
|
+
|
|
94
|
+
if (name) {
|
|
95
|
+
if (!params.has(name)) {
|
|
96
|
+
const p = makeNode();
|
|
97
|
+
params.set(name, p);
|
|
98
|
+
}
|
|
99
|
+
return new ParamRefNode(name);
|
|
100
|
+
}
|
|
101
|
+
return new LiteralNode(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
maybeSelection(value) {
|
|
105
|
+
return this.maybeParam(value, () => new SelectionNode);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
error(message, data) {
|
|
109
|
+
error(message, data);
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/util.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function paramRef(value) {
|
|
2
|
+
const type = typeof value;
|
|
3
|
+
return type === 'object' ? value?.param
|
|
4
|
+
: type === 'string' ? paramStr(value)
|
|
5
|
+
: null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function paramStr(value) {
|
|
9
|
+
return value?.[0] === '$' ? value.slice(1) : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function toParamRef(name) {
|
|
13
|
+
return `$${name}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function toArray(value) {
|
|
17
|
+
return [value].flat();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isArray(value) {
|
|
21
|
+
return Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isObject(value) {
|
|
25
|
+
return value !== null && typeof value === 'object' && !isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isNumber(value) {
|
|
29
|
+
return typeof value === 'number';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isNumberOrString(value) {
|
|
33
|
+
const t = typeof value;
|
|
34
|
+
return t === 'number' || t === 'string';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isString(value) {
|
|
38
|
+
return typeof value === 'string';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isFunction(value) {
|
|
42
|
+
return typeof value === 'function';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function error(message, data) {
|
|
46
|
+
throw Object.assign(Error(message), { data });
|
|
47
|
+
}
|