@uwdata/vgplot 0.4.0 → 0.6.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/README.md +4 -2
- package/dist/vgplot.js +6677 -6503
- package/dist/vgplot.min.js +12 -33
- package/package.json +8 -10
- package/src/api.js +347 -0
- package/src/connect.js +14 -0
- package/src/context.js +20 -0
- package/src/index.js +14 -303
- package/src/inputs.js +24 -0
- package/src/{directives → plot}/attributes.js +32 -5
- package/src/{directives → plot}/interactors.js +8 -6
- package/src/{directives → plot}/legends.js +14 -6
- package/src/{directives → plot}/marks.js +18 -13
- package/src/plot/named-plots.js +49 -0
- package/src/plot/plot.js +9 -0
- package/src/directives/plot.js +0 -39
- package/src/interactors/Highlight.js +0 -101
- package/src/interactors/Interval1D.js +0 -90
- package/src/interactors/Interval2D.js +0 -102
- package/src/interactors/Nearest.js +0 -66
- package/src/interactors/PanZoom.js +0 -121
- package/src/interactors/Toggle.js +0 -111
- package/src/interactors/util/brush.js +0 -45
- package/src/interactors/util/close-to.js +0 -9
- package/src/interactors/util/get-field.js +0 -4
- package/src/interactors/util/invert.js +0 -3
- package/src/interactors/util/patchScreenCTM.js +0 -13
- package/src/interactors/util/sanitize-styles.js +0 -9
- package/src/interactors/util/to-kebab-case.js +0 -9
- package/src/layout/index.js +0 -2
- package/src/legend.js +0 -64
- package/src/marks/ConnectedMark.js +0 -63
- package/src/marks/ContourMark.js +0 -89
- package/src/marks/DenseLineMark.js +0 -146
- package/src/marks/Density1DMark.js +0 -104
- package/src/marks/Density2DMark.js +0 -69
- package/src/marks/Grid2DMark.js +0 -191
- package/src/marks/HexbinMark.js +0 -88
- package/src/marks/Mark.js +0 -195
- package/src/marks/RasterMark.js +0 -122
- package/src/marks/RasterTileMark.js +0 -332
- package/src/marks/RegressionMark.js +0 -117
- package/src/marks/util/bin-field.js +0 -17
- package/src/marks/util/density.js +0 -226
- package/src/marks/util/extent.js +0 -56
- package/src/marks/util/grid.js +0 -57
- package/src/marks/util/handle-param.js +0 -14
- package/src/marks/util/is-arrow-table.js +0 -3
- package/src/marks/util/is-color.js +0 -18
- package/src/marks/util/is-constant-option.js +0 -40
- package/src/marks/util/is-symbol.js +0 -20
- package/src/marks/util/raster.js +0 -44
- package/src/marks/util/stats.js +0 -133
- package/src/marks/util/to-data-array.js +0 -58
- package/src/plot-attributes.js +0 -211
- package/src/plot-renderer.js +0 -161
- package/src/plot.js +0 -136
- package/src/spec/parse-data.js +0 -69
- package/src/spec/parse-spec.js +0 -422
- package/src/spec/to-module.js +0 -465
- package/src/spec/util.js +0 -43
- package/src/symbols.js +0 -3
- package/src/transforms/bin.js +0 -81
- package/src/transforms/index.js +0 -3
- /package/src/{directives → plot}/data.js +0 -0
package/src/spec/to-module.js
DELETED
|
@@ -1,465 +0,0 @@
|
|
|
1
|
-
import { create } from '@uwdata/mosaic-sql';
|
|
2
|
-
import { parseData } from './parse-data.js';
|
|
3
|
-
import { ParseContext } from './parse-spec.js';
|
|
4
|
-
import {
|
|
5
|
-
error, paramRef, toArray,
|
|
6
|
-
isArray, isNumberOrString, isObject, isString, isFunction
|
|
7
|
-
} from './util.js';
|
|
8
|
-
|
|
9
|
-
const TOPOJSON = 'https://cdn.jsdelivr.net/npm/topojson@3.0.2/+esm';
|
|
10
|
-
|
|
11
|
-
const SpecParsers = new Map([
|
|
12
|
-
['plot', { type: isArray, parse: parsePlot }],
|
|
13
|
-
['mark', { type: isString, parse: parseNakedMark }],
|
|
14
|
-
['legend', { type: isString, parse: parseLegend }],
|
|
15
|
-
['hconcat', { type: isArray, parse: parseHConcat }],
|
|
16
|
-
['vconcat', { type: isArray, parse: parseVConcat }],
|
|
17
|
-
['hspace', { type: isNumberOrString, parse: parseHSpace }],
|
|
18
|
-
['vspace', { type: isNumberOrString, parse: parseVSpace }],
|
|
19
|
-
['input', { type: isString, parse: parseInput }]
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
const DataFormats = new Map([
|
|
23
|
-
['csv', parseCSVData],
|
|
24
|
-
['json', parseJSONData],
|
|
25
|
-
['geojson', parseGeoJSONData],
|
|
26
|
-
['topojson', parseTopoJSONData],
|
|
27
|
-
['parquet', parseParquetData],
|
|
28
|
-
['table', parseTableData]
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
export function specToModule(spec, options) {
|
|
32
|
-
spec = isString(spec) ? JSON.parse(spec) : spec;
|
|
33
|
-
return new CodegenContext(options).generate(spec);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function maybeNewline(entry) {
|
|
37
|
-
return entry?.length ? [''] : [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class CodegenContext extends ParseContext {
|
|
41
|
-
constructor(options) {
|
|
42
|
-
super({
|
|
43
|
-
specParsers: SpecParsers,
|
|
44
|
-
dataFormats: DataFormats,
|
|
45
|
-
...options
|
|
46
|
-
});
|
|
47
|
-
this.imports = options?.imports || new Map([
|
|
48
|
-
['@uwdata/vgplot', '* as vg']
|
|
49
|
-
]);
|
|
50
|
-
this.depth = 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async generate(input) {
|
|
54
|
-
// eslint-disable-next-line no-unused-vars
|
|
55
|
-
const { meta, data = {}, plotDefaults = {}, params, ...spec } = input;
|
|
56
|
-
|
|
57
|
-
// parse data definitions
|
|
58
|
-
const dataCode = await Promise.all(
|
|
59
|
-
Object.keys(data).flatMap(name => {
|
|
60
|
-
const q = parseData(name, data[name], this);
|
|
61
|
-
return !q ? []
|
|
62
|
-
: q.data ? `const ${name} = ${q.data};`
|
|
63
|
-
: `await vg.coordinator().exec(\n ${q}\n);`;
|
|
64
|
-
})
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// parse default attributes
|
|
68
|
-
const defaultList = Object.keys(plotDefaults)
|
|
69
|
-
.map(key => parseAttribute(plotDefaults, key, this));
|
|
70
|
-
let defaultCode = [];
|
|
71
|
-
if (defaultList.length) {
|
|
72
|
-
this.plotDefaults = 'defaultAttributes';
|
|
73
|
-
defaultCode = [
|
|
74
|
-
'const defaultAttributes = [',
|
|
75
|
-
defaultList.map(d => ' ' + d).join(',\n'),
|
|
76
|
-
'];'
|
|
77
|
-
];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// parse param/selection definitions
|
|
81
|
-
for (const name in params) {
|
|
82
|
-
this.params.set(`$${name}`, parseParam(params[name], this));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const specCode = [
|
|
86
|
-
`export default ${parseSpec(spec, this)};`
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
const paramCode = [];
|
|
90
|
-
for (const [key, value] of this.params) {
|
|
91
|
-
paramCode.push(`const ${key} = ${value};`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const importsCode = [];
|
|
95
|
-
for (const [pkg, methods] of this.imports) {
|
|
96
|
-
importsCode.push(
|
|
97
|
-
isString(methods)
|
|
98
|
-
? `import ${methods} from "${pkg}";`
|
|
99
|
-
: `import { ${methods.join(', ')} } from "${pkg}";`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return [
|
|
104
|
-
...importsCode,
|
|
105
|
-
...maybeNewline(importsCode),
|
|
106
|
-
...dataCode,
|
|
107
|
-
...maybeNewline(dataCode),
|
|
108
|
-
...paramCode,
|
|
109
|
-
...maybeNewline(paramCode),
|
|
110
|
-
...defaultCode,
|
|
111
|
-
...maybeNewline(defaultCode),
|
|
112
|
-
...specCode
|
|
113
|
-
].join('\n');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
addImport(pkg, method) {
|
|
117
|
-
if (!this.imports.has(pkg)) {
|
|
118
|
-
this.imports.set(pkg, []);
|
|
119
|
-
}
|
|
120
|
-
this.imports.get(pkg).push(method);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
setImports(pkg, all) {
|
|
124
|
-
this.imports.set(pkg, all);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
maybeParam(value, ctr = 'vg.Param.value()') {
|
|
128
|
-
const { params } = this;
|
|
129
|
-
const name = paramRef(value);
|
|
130
|
-
if (name) {
|
|
131
|
-
const $name = `$${name}`;
|
|
132
|
-
if (!params.has($name)) {
|
|
133
|
-
params.set($name, ctr);
|
|
134
|
-
}
|
|
135
|
-
return $name;
|
|
136
|
-
}
|
|
137
|
-
return JSON.stringify(value);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
maybeSelection(value) {
|
|
141
|
-
return this.maybeParam(value, 'vg.Selection.intersect()');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
maybeTransform(value) {
|
|
145
|
-
if (isObject(value)) {
|
|
146
|
-
return value.expr ? parseExpression(value, this)
|
|
147
|
-
: value.agg ? parseExpression(value, this, 'agg', 'agg')
|
|
148
|
-
: parseTransform(value, this);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
indent() {
|
|
153
|
-
this.depth += 1;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
undent() {
|
|
157
|
-
this.depth -= 1;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
tab() {
|
|
161
|
-
return Array.from({ length: this.depth }, () => ' ').join('');
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function parseExpression(spec, ctx, key = 'expr', method = 'sql') {
|
|
166
|
-
const { label } = spec;
|
|
167
|
-
const expr = spec[key]
|
|
168
|
-
const tokens = expr.split(/(\\'|\\"|"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\$\w+)/g);
|
|
169
|
-
let str = '';
|
|
170
|
-
|
|
171
|
-
for (let i = 0; i < tokens.length; ++i) {
|
|
172
|
-
const tok = tokens[i];
|
|
173
|
-
if (tok.startsWith('$')) {
|
|
174
|
-
str += `\${${ctx.maybeParam(tok)}}`;
|
|
175
|
-
} else {
|
|
176
|
-
str += tok;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return `vg.${method}\`${str}\``
|
|
181
|
-
+ (label ? `.annotate({ label: ${JSON.stringify(label)} })` : '');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function parseTransform(spec, ctx) {
|
|
185
|
-
const { transforms } = ctx;
|
|
186
|
-
let name;
|
|
187
|
-
for (const key in spec) {
|
|
188
|
-
if (transforms.has(key)) {
|
|
189
|
-
name = key;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (!name) {
|
|
193
|
-
return; // return undefined to signal no transform
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const args = name === 'count' || name == null ? [] : toArray(spec[name]);
|
|
197
|
-
let str = `vg.${name}(`
|
|
198
|
-
+ args.map(v => ctx.maybeParam(v)).join(', ')
|
|
199
|
-
+ ')';
|
|
200
|
-
|
|
201
|
-
if (spec.distinct) {
|
|
202
|
-
str += '.distinct()'
|
|
203
|
-
}
|
|
204
|
-
if (spec.orderby) {
|
|
205
|
-
const p = toArray(spec.orderby).map(v => ctx.maybeParam(v));
|
|
206
|
-
str += `.orderby(${p.join(', ')})`;
|
|
207
|
-
}
|
|
208
|
-
if (spec.partitionby) {
|
|
209
|
-
const p = toArray(spec.partitionby).map(v => ctx.maybeParam(v));
|
|
210
|
-
str += `.partitionby(${p.join(', ')})`;
|
|
211
|
-
}
|
|
212
|
-
if (spec.rows) {
|
|
213
|
-
str += `.rows(${ctx.maybeParam(spec.rows)})`;
|
|
214
|
-
} else if (spec.range) {
|
|
215
|
-
str += `.range(${ctx.maybeParam(spec.rows)})`;
|
|
216
|
-
}
|
|
217
|
-
return str;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function parseParam(param, ctx) {
|
|
221
|
-
param = isObject(param) ? param : { value: param };
|
|
222
|
-
const { select = 'value' } = param;
|
|
223
|
-
const parser = ctx.paramParsers.get(select);
|
|
224
|
-
if (!parser) {
|
|
225
|
-
error(`Unrecognized param type: ${select}`, param);
|
|
226
|
-
}
|
|
227
|
-
if (select === 'value') {
|
|
228
|
-
const { value, date } = param;
|
|
229
|
-
return Array.isArray(value)
|
|
230
|
-
? `vg.Param.array([${value.map(v => ctx.maybeParam(v)).join(', ')}])`
|
|
231
|
-
: date ? `vg.Param.value(new Date(${JSON.stringify(date)}))`
|
|
232
|
-
: `vg.Param.value(${JSON.stringify(value)})`;
|
|
233
|
-
} else {
|
|
234
|
-
return `vg.Selection.${select}()`;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function dataOptions(options) {
|
|
239
|
-
const opt = [];
|
|
240
|
-
for (const key in options) {
|
|
241
|
-
opt.push(`${key}: ${JSON.stringify(options[key])}`);
|
|
242
|
-
}
|
|
243
|
-
return opt.length ? `, { ${opt.join(', ')} }` : '';
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function parseTableData(name, spec) {
|
|
247
|
-
// eslint-disable-next-line no-unused-vars
|
|
248
|
-
const { query, type, ...options } = spec;
|
|
249
|
-
if (query) {
|
|
250
|
-
return `\`${create(name, query, options)}\``;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function parseParquetData(name, spec) {
|
|
255
|
-
// eslint-disable-next-line no-unused-vars
|
|
256
|
-
const { file, type, ...options } = spec;
|
|
257
|
-
return `vg.loadParquet("${name}", "${file}"${dataOptions(options)})`;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function parseCSVData(name, spec) {
|
|
261
|
-
// eslint-disable-next-line no-unused-vars
|
|
262
|
-
const { file, type, ...options } = spec;
|
|
263
|
-
return `vg.loadCSV("${name}", "${file}"${dataOptions(options)})`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function parseJSONData(name, spec) {
|
|
267
|
-
// eslint-disable-next-line no-unused-vars
|
|
268
|
-
const { data, file, type, ...options } = spec;
|
|
269
|
-
const opt = dataOptions(options);
|
|
270
|
-
if (data) {
|
|
271
|
-
const d = '[\n '
|
|
272
|
-
+ data.map(d => JSON.stringify(d)).join(',\n ')
|
|
273
|
-
+ '\n ]';
|
|
274
|
-
return `vg.loadObjects("${name}", ${d}${opt})`;
|
|
275
|
-
} else {
|
|
276
|
-
return `vg.loadCSV("${name}", "${file}"${opt})`;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function fetchJSON(spec) {
|
|
281
|
-
const { data, file } = spec;
|
|
282
|
-
return data
|
|
283
|
-
? JSON.stringify(data)
|
|
284
|
-
: `await fetch("${file}")\n .then(r => r.json())`;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function parseGeoJSONData(name, spec, ctx) {
|
|
288
|
-
ctx.datasets.set(name, name);
|
|
289
|
-
return { data: fetchJSON(spec) };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function parseTopoJSONData(name, spec, ctx) {
|
|
293
|
-
ctx.datasets.set(name, name);
|
|
294
|
-
const json = fetchJSON(spec);
|
|
295
|
-
let data;
|
|
296
|
-
if (spec.feature) {
|
|
297
|
-
ctx.addImport(TOPOJSON, 'feature');
|
|
298
|
-
const object = `json.objects['${spec.feature}']`;
|
|
299
|
-
data = json + `\n .then(json => feature(json, ${object}).features)`;
|
|
300
|
-
} else {
|
|
301
|
-
ctx.addImport(TOPOJSON, 'mesh');
|
|
302
|
-
const object = spec.mesh ? `json.objects['${spec.mesh}']` : 'undefined';
|
|
303
|
-
const filter = spec.filter === 'interior' ? ', (a, b) => a !== b'
|
|
304
|
-
: spec.filter === 'exterior' ? ', (a, b) => a === b'
|
|
305
|
-
: '';
|
|
306
|
-
data = json + `\n .then(json => [mesh(json, ${object}${filter})])`;
|
|
307
|
-
}
|
|
308
|
-
return { data };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function parseSpec(spec, ctx) {
|
|
312
|
-
for (const [key, { type, parse }] of ctx.specParsers) {
|
|
313
|
-
const value = spec[key];
|
|
314
|
-
if (value != null) {
|
|
315
|
-
if (type(value)) {
|
|
316
|
-
return parse(spec, ctx);
|
|
317
|
-
} else {
|
|
318
|
-
error(`Invalid property type: ${key}`, spec);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
error(`Invalid specification.`, spec);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function parseHSpace(spec, ctx) {
|
|
326
|
-
return `${ctx.tab()}vg.hspace(${spec.hspace})`;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function parseVSpace(spec, ctx) {
|
|
330
|
-
return `${ctx.tab()}vg.vspace(${spec.vspace})`;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function parseInput(spec, ctx) {
|
|
334
|
-
const { input, ...options } = spec;
|
|
335
|
-
const fn = ctx.inputs.get(input);
|
|
336
|
-
if (!isFunction(fn)) {
|
|
337
|
-
error(`Unrecognized input: ${input}`, spec);
|
|
338
|
-
}
|
|
339
|
-
const opt = [];
|
|
340
|
-
for (const key in options) {
|
|
341
|
-
opt.push(`${key}: ${ctx.maybeSelection(options[key])}`);
|
|
342
|
-
}
|
|
343
|
-
return `${ctx.tab()}vg.${input}({ ${opt.join(', ')} })`;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function parseVConcat(spec, ctx) {
|
|
347
|
-
ctx.indent();
|
|
348
|
-
const items = spec.vconcat.map(s => parseSpec(s, ctx));
|
|
349
|
-
ctx.undent();
|
|
350
|
-
return `${ctx.tab()}vg.vconcat(\n${items.join(',\n')}\n${ctx.tab()})`;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function parseHConcat(spec, ctx) {
|
|
354
|
-
ctx.indent();
|
|
355
|
-
const items = spec.hconcat.map(s => parseSpec(s, ctx));
|
|
356
|
-
ctx.undent();
|
|
357
|
-
return `${ctx.tab()}vg.hconcat(\n${items.join(',\n')}\n${ctx.tab()})`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function parsePlot(spec, ctx) {
|
|
361
|
-
const { plot, ...attributes } = spec;
|
|
362
|
-
|
|
363
|
-
ctx.indent();
|
|
364
|
-
const attrs = [
|
|
365
|
-
...(ctx.plotDefaults ? [`${ctx.tab()}...defaultAttributes`] : []),
|
|
366
|
-
...Object.keys(attributes).map(key => parseAttribute(spec, key, ctx))
|
|
367
|
-
];
|
|
368
|
-
const entries = plot.map(e => parseEntry(e, ctx));
|
|
369
|
-
const items = entries.concat(attrs);
|
|
370
|
-
ctx.undent();
|
|
371
|
-
|
|
372
|
-
return `${ctx.tab()}vg.plot(\n${items.join(',\n')}\n${ctx.tab()})`;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function parseNakedMark(spec, ctx) {
|
|
376
|
-
return parsePlot({ plot: [spec] }, ctx);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function parseLegend(spec, ctx) {
|
|
380
|
-
const { legend, ...options } = spec;
|
|
381
|
-
const type = `${legend}Legend`;
|
|
382
|
-
if (!isFunction(ctx.legends.get(type))) {
|
|
383
|
-
error(`Unrecognized legend type: ${legend}`, spec);
|
|
384
|
-
}
|
|
385
|
-
const opt = [];
|
|
386
|
-
for (const key in options) {
|
|
387
|
-
opt.push(`${key}: ${ctx.maybeSelection(options[key])}`);
|
|
388
|
-
}
|
|
389
|
-
return `${ctx.tab()}vg.${type}({ ${opt.join(', ')} })`;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function parseAttribute(spec, name, ctx) {
|
|
393
|
-
const fn = ctx.attributes.get(name);
|
|
394
|
-
if (!isFunction(fn)) {
|
|
395
|
-
error(`Unrecognized attribute: ${name}`, spec);
|
|
396
|
-
}
|
|
397
|
-
const value = spec[name];
|
|
398
|
-
const arg = value === 'Fixed' ? 'vg.Fixed' : ctx.maybeParam(value);
|
|
399
|
-
return `${ctx.tab()}vg.${name}(${arg})`;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function parseEntry(spec, ctx) {
|
|
403
|
-
return isString(spec.mark) ? parseMark(spec, ctx)
|
|
404
|
-
: isString(spec.legend) ? parseLegend(spec, ctx)
|
|
405
|
-
: isString(spec.select) ? parseInteractor(spec, ctx)
|
|
406
|
-
: error(`Invalid plot entry.`, spec);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function parseMark(spec, ctx) {
|
|
410
|
-
const { mark, data, ...options } = spec;
|
|
411
|
-
|
|
412
|
-
if (!isFunction(ctx.marks.get(mark))) {
|
|
413
|
-
error(`Unrecognized mark type: ${mark}`, spec);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const input = parseMarkData(data, ctx);
|
|
417
|
-
const opt = [];
|
|
418
|
-
for (const key in options) {
|
|
419
|
-
opt.push(`${key}: ${parseMarkOption(options[key], ctx)}`);
|
|
420
|
-
}
|
|
421
|
-
const d = input || '';
|
|
422
|
-
const o = opt.length ? `{ ${opt.join(', ')} }` : '';
|
|
423
|
-
let arg = `${d}${o}`;
|
|
424
|
-
if (d && o) {
|
|
425
|
-
ctx.indent();
|
|
426
|
-
arg = `\n${ctx.tab()}${d},\n${ctx.tab()}${o}\n`;
|
|
427
|
-
ctx.undent();
|
|
428
|
-
arg += ctx.tab();
|
|
429
|
-
}
|
|
430
|
-
return `${ctx.tab()}vg.${mark}(${arg})`;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function parseMarkData(spec, ctx) {
|
|
434
|
-
if (!spec) return null; // no data, likely a decoration mark
|
|
435
|
-
if (isArray(spec)) return JSON.stringify(spec); // data provided directly
|
|
436
|
-
const { from: table, ...options } = spec;
|
|
437
|
-
if (ctx.datasets.has(table)) {
|
|
438
|
-
// client-managed data, simply pass through
|
|
439
|
-
return ctx.datasets.get(table);
|
|
440
|
-
} else {
|
|
441
|
-
// source-managed data, create from descriptor
|
|
442
|
-
const opt = [];
|
|
443
|
-
for (const key in options) {
|
|
444
|
-
opt.push(`${key}: ${ctx.maybeSelection(options[key])}`);
|
|
445
|
-
}
|
|
446
|
-
const arg = opt.length ? `, { ${opt.join(', ')} }` : '';
|
|
447
|
-
return `vg.from("${table}"${arg})`;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function parseMarkOption(spec, ctx) {
|
|
452
|
-
return ctx.maybeTransform(spec) || ctx.maybeParam(spec);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function parseInteractor(spec, ctx) {
|
|
456
|
-
const { select, ...options } = spec;
|
|
457
|
-
if (!isFunction(ctx.interactors.get(select))) {
|
|
458
|
-
error(`Unrecognized interactor type: ${select}`, spec);
|
|
459
|
-
}
|
|
460
|
-
const opt = [];
|
|
461
|
-
for (const key in options) {
|
|
462
|
-
opt.push(`${key}: ${ctx.maybeSelection(options[key])}`);
|
|
463
|
-
}
|
|
464
|
-
return `${ctx.tab()}vg.${select}({ ${opt.join(', ')} })`;
|
|
465
|
-
}
|
package/src/spec/util.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
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 toArray(value) {
|
|
13
|
-
return [value].flat();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function isArray(value) {
|
|
17
|
-
return Array.isArray(value);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function isObject(value) {
|
|
21
|
-
return value !== null && typeof value === 'object' && !isArray(value);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function isNumber(value) {
|
|
25
|
-
return typeof value === 'number';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function isNumberOrString(value) {
|
|
29
|
-
const t = typeof value;
|
|
30
|
-
return t === 'number' || t === 'string';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function isString(value) {
|
|
34
|
-
return typeof value === 'string';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function isFunction(value) {
|
|
38
|
-
return typeof value === 'function';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function error(message, data) {
|
|
42
|
-
throw Object.assign(Error(message), { data });
|
|
43
|
-
}
|
package/src/symbols.js
DELETED
package/src/transforms/bin.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { asColumn } from '@uwdata/mosaic-sql';
|
|
2
|
-
import { Transform } from '../symbols.js';
|
|
3
|
-
|
|
4
|
-
const EXTENT = [
|
|
5
|
-
'rectY-x', 'rectX-y', 'rect-x', 'rect-y'
|
|
6
|
-
];
|
|
7
|
-
|
|
8
|
-
function hasExtent(channel, type) {
|
|
9
|
-
return EXTENT.includes(`${type}-${channel}`);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function bin(field, options = { steps: 25 }) {
|
|
13
|
-
const fn = (mark, channel) => {
|
|
14
|
-
return hasExtent(channel, mark.type)
|
|
15
|
-
? {
|
|
16
|
-
[`${channel}1`]: binField(mark, field, options),
|
|
17
|
-
[`${channel}2`]: binField(mark, field, { ...options, offset: 1 })
|
|
18
|
-
}
|
|
19
|
-
: {
|
|
20
|
-
[channel]: binField(mark, field, options)
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
fn[Transform] = true;
|
|
24
|
-
return fn;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function binField(mark, column, options) {
|
|
28
|
-
return {
|
|
29
|
-
column,
|
|
30
|
-
label: column,
|
|
31
|
-
get stats() { return ['min', 'max']; },
|
|
32
|
-
get columns() { return [column]; },
|
|
33
|
-
get basis() { return column; },
|
|
34
|
-
toString() {
|
|
35
|
-
const { min, max } = mark.stats[column];
|
|
36
|
-
const b = bins(min, max, options);
|
|
37
|
-
const col = asColumn(column);
|
|
38
|
-
const base = b.min === 0 ? col : `(${col} - ${b.min})`;
|
|
39
|
-
const alpha = `${(b.max - b.min) / b.steps}::DOUBLE`;
|
|
40
|
-
const off = options.offset ? `${options.offset} + ` : '';
|
|
41
|
-
return `${b.min} + ${alpha} * (${off}FLOOR(${base} / ${alpha})::INTEGER)`;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function bins(min, max, options) {
|
|
47
|
-
let { steps = 25, minstep = 0, nice = true } = options;
|
|
48
|
-
|
|
49
|
-
if (nice !== false) {
|
|
50
|
-
// use span to determine step size
|
|
51
|
-
const span = max - min;
|
|
52
|
-
const maxb = steps;
|
|
53
|
-
const logb = Math.LN10;
|
|
54
|
-
const level = Math.ceil(Math.log(maxb) / logb);
|
|
55
|
-
let step = Math.max(
|
|
56
|
-
minstep,
|
|
57
|
-
Math.pow(10, Math.round(Math.log(span) / logb) - level)
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
// increase step size if too many bins
|
|
61
|
-
while (Math.ceil(span / step) > maxb) { step *= 10; }
|
|
62
|
-
|
|
63
|
-
// decrease step size if allowed
|
|
64
|
-
const div = [5, 2];
|
|
65
|
-
let v;
|
|
66
|
-
for (let i = 0, n = div.length; i < n; ++i) {
|
|
67
|
-
v = step / div[i];
|
|
68
|
-
if (v >= minstep && span / v <= maxb) step = v;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
v = Math.log(step);
|
|
72
|
-
const precision = v >= 0 ? 0 : ~~(-v / logb) + 1;
|
|
73
|
-
const eps = Math.pow(10, -precision - 1);
|
|
74
|
-
v = Math.floor(min / step + eps) * step;
|
|
75
|
-
min = min < v ? v - step : v;
|
|
76
|
-
max = Math.ceil(max / step) * step;
|
|
77
|
-
steps = Math.round((max - min) / step);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return { min, max, steps };
|
|
81
|
-
}
|
package/src/transforms/index.js
DELETED
|
File without changes
|