@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.
@@ -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,12 @@
1
+ /**
2
+ * Set of input widget type names.
3
+ */
4
+ export function inputNames(overrides = []) {
5
+ return new Set([
6
+ 'menu',
7
+ 'search',
8
+ 'slider',
9
+ 'table',
10
+ ...overrides
11
+ ]);
12
+ }
@@ -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
+ }
@@ -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
+ }