@uwdata/mosaic-plot 0.12.1 → 0.13.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/dist/mosaic-plot.js +340 -239
- package/dist/mosaic-plot.min.js +8 -8
- package/package.json +9 -12
- package/src/interactors/Interval1D.js +4 -0
- package/src/interactors/Interval2D.js +4 -0
- package/src/interactors/Nearest.js +10 -4
- package/src/interactors/PanZoom.js +17 -10
- package/src/interactors/Region.js +4 -0
- package/src/interactors/Toggle.js +10 -3
- package/src/marks/ConnectedMark.js +1 -1
- package/src/marks/Density1DMark.js +68 -21
- package/src/marks/Mark.js +8 -4
- package/src/marks/util/grid.js +36 -7
- package/src/marks/util/is-constant-option.js +2 -0
- package/src/plot.js +14 -3
- package/vitest.config.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uwdata/mosaic-plot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "A Mosaic-powered plotting framework based on Observable Plot.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data",
|
|
@@ -12,27 +12,24 @@
|
|
|
12
12
|
"license": "BSD-3-Clause",
|
|
13
13
|
"author": "Jeffrey Heer (https://idl.uw.edu)",
|
|
14
14
|
"type": "module",
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"unpkg": "dist/mosaic-plot.min.js",
|
|
15
|
+
"exports": {
|
|
16
|
+
"default": "./src/index.js"
|
|
17
|
+
},
|
|
19
18
|
"repository": {
|
|
20
19
|
"type": "git",
|
|
21
20
|
"url": "https://github.com/uwdata/mosaic.git"
|
|
22
21
|
},
|
|
23
22
|
"scripts": {
|
|
24
|
-
"prebuild": "rimraf dist && mkdir dist",
|
|
25
|
-
"build": "node ../../esbuild.js mosaic-plot",
|
|
26
23
|
"lint": "eslint src test",
|
|
27
24
|
"test": "vitest run",
|
|
28
|
-
"prepublishOnly": "npm run test && npm run lint
|
|
25
|
+
"prepublishOnly": "npm run test && npm run lint"
|
|
29
26
|
},
|
|
30
27
|
"dependencies": {
|
|
31
|
-
"@observablehq/plot": "^0.6.
|
|
32
|
-
"@uwdata/mosaic-core": "^0.
|
|
33
|
-
"@uwdata/mosaic-sql": "^0.
|
|
28
|
+
"@observablehq/plot": "^0.6.17",
|
|
29
|
+
"@uwdata/mosaic-core": "^0.13.0",
|
|
30
|
+
"@uwdata/mosaic-sql": "^0.13.0",
|
|
34
31
|
"d3": "^7.9.0",
|
|
35
32
|
"isoformat": "^0.2.1"
|
|
36
33
|
},
|
|
37
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "b5a0e03e200c0f04c46562a288f084ffc9f6ad55"
|
|
38
35
|
}
|
|
@@ -6,6 +6,10 @@ import { getField } from './util/get-field.js';
|
|
|
6
6
|
import { invert } from './util/invert.js';
|
|
7
7
|
import { sanitizeStyles } from './util/sanitize-styles.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @import {Activatable} from '@uwdata/mosaic-core'
|
|
11
|
+
* @implements {Activatable}
|
|
12
|
+
*/
|
|
9
13
|
export class Interval1D {
|
|
10
14
|
constructor(mark, {
|
|
11
15
|
channel,
|
|
@@ -6,6 +6,10 @@ import { getField } from './util/get-field.js';
|
|
|
6
6
|
import { invert } from './util/invert.js';
|
|
7
7
|
import { sanitizeStyles } from './util/sanitize-styles.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @import {Activatable} from '@uwdata/mosaic-core'
|
|
11
|
+
* @implements {Activatable}
|
|
12
|
+
*/
|
|
9
13
|
export class Interval2D {
|
|
10
14
|
constructor(mark, {
|
|
11
15
|
selection,
|
|
@@ -2,6 +2,10 @@ import { clausePoint, clausePoints, isSelection } from '@uwdata/mosaic-core';
|
|
|
2
2
|
import { select, pointer, min } from 'd3';
|
|
3
3
|
import { getField } from './util/get-field.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @import {Activatable} from '@uwdata/mosaic-core'
|
|
7
|
+
* @implements {Activatable}
|
|
8
|
+
*/
|
|
5
9
|
export class Nearest {
|
|
6
10
|
constructor(mark, {
|
|
7
11
|
selection,
|
|
@@ -71,12 +75,14 @@ export class Nearest {
|
|
|
71
75
|
|
|
72
76
|
// trigger activation updates
|
|
73
77
|
svg.addEventListener('pointerenter', evt => {
|
|
74
|
-
if (!evt.buttons)
|
|
75
|
-
const v = this.channels.map(() => 0);
|
|
76
|
-
selection.activate(this.clause(v));
|
|
77
|
-
}
|
|
78
|
+
if (!evt.buttons) this.activate();
|
|
78
79
|
});
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
activate() {
|
|
83
|
+
const v = this.channels.map(() => 0);
|
|
84
|
+
this.selection.activate(this.clause(v));
|
|
85
|
+
}
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
/**
|
|
@@ -4,6 +4,10 @@ import { getField } from './util/get-field.js';
|
|
|
4
4
|
|
|
5
5
|
const asc = (a, b) => a - b;
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @import {Activatable} from '@uwdata/mosaic-core'
|
|
9
|
+
* @implements {Activatable}
|
|
10
|
+
*/
|
|
7
11
|
export class PanZoom {
|
|
8
12
|
constructor(mark, {
|
|
9
13
|
x = new Selection(),
|
|
@@ -59,7 +63,7 @@ export class PanZoom {
|
|
|
59
63
|
this.svg = svg;
|
|
60
64
|
if (this.initialized) return; else this.initialized = true;
|
|
61
65
|
|
|
62
|
-
const { panx, pany, mark: { plot: { element } }
|
|
66
|
+
const { panx, pany, mark: { plot: { element } } } = this;
|
|
63
67
|
|
|
64
68
|
this.xscale = svg.scale('x');
|
|
65
69
|
this.yscale = svg.scale('y');
|
|
@@ -85,19 +89,22 @@ export class PanZoom {
|
|
|
85
89
|
let enter = false;
|
|
86
90
|
element.addEventListener('pointerenter', evt => {
|
|
87
91
|
if (enter) return; else enter = true;
|
|
88
|
-
if (evt.buttons)
|
|
89
|
-
if (panx) {
|
|
90
|
-
const { xscale, xfield } = this;
|
|
91
|
-
xsel.activate(this.clause(xscale.domain, xfield, xscale));
|
|
92
|
-
}
|
|
93
|
-
if (pany) {
|
|
94
|
-
const { yscale, yfield } = this;
|
|
95
|
-
ysel.activate(this.clause(yscale.domain, yfield, yscale));
|
|
96
|
-
}
|
|
92
|
+
if (!evt.buttons) this.activate(); // don't activate if mouse down
|
|
97
93
|
});
|
|
98
94
|
element.addEventListener('pointerleave', () => enter = false);
|
|
99
95
|
}
|
|
100
96
|
}
|
|
97
|
+
|
|
98
|
+
activate() {
|
|
99
|
+
if (this.panx) {
|
|
100
|
+
const { xscale, xfield } = this;
|
|
101
|
+
this.xsel.activate(this.clause(xscale.domain, xfield, xscale));
|
|
102
|
+
}
|
|
103
|
+
if (this.pany) {
|
|
104
|
+
const { yscale, yfield } = this;
|
|
105
|
+
this.ysel.activate(this.clause(yscale.domain, yfield, yscale));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
function extent(ext, defaultTrue, defaultFalse) {
|
|
@@ -8,6 +8,10 @@ import { sanitizeStyles } from './util/sanitize-styles.js';
|
|
|
8
8
|
import { neqSome } from './util/neq.js';
|
|
9
9
|
import { getDatum } from './util/get-datum.js';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @import {Activatable} from '@uwdata/mosaic-core'
|
|
13
|
+
* @implements {Activatable}
|
|
14
|
+
*/
|
|
11
15
|
export class Region {
|
|
12
16
|
constructor(mark, {
|
|
13
17
|
channels,
|
|
@@ -2,6 +2,10 @@ import { clausePoints } from '@uwdata/mosaic-core';
|
|
|
2
2
|
import { getDatum } from './util/get-datum.js';
|
|
3
3
|
import { neq, neqSome } from './util/neq.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @import {Activatable} from '@uwdata/mosaic-core'
|
|
7
|
+
* @implements {Activatable}
|
|
8
|
+
*/
|
|
5
9
|
export class Toggle {
|
|
6
10
|
/**
|
|
7
11
|
* @param {*} mark The mark to interact with.
|
|
@@ -12,8 +16,8 @@ export class Toggle {
|
|
|
12
16
|
channels,
|
|
13
17
|
peers = true
|
|
14
18
|
}) {
|
|
15
|
-
this.value = null;
|
|
16
19
|
this.mark = mark;
|
|
20
|
+
this.value = null;
|
|
17
21
|
this.selection = selection;
|
|
18
22
|
this.peers = peers;
|
|
19
23
|
const fields = this.fields = [];
|
|
@@ -75,10 +79,13 @@ export class Toggle {
|
|
|
75
79
|
});
|
|
76
80
|
|
|
77
81
|
svg.addEventListener('pointerenter', evt => {
|
|
78
|
-
if (evt.buttons)
|
|
79
|
-
this.selection.activate(this.clause([this.fields.map(() => 0)]));
|
|
82
|
+
if (!evt.buttons) this.activate();
|
|
80
83
|
});
|
|
81
84
|
}
|
|
85
|
+
|
|
86
|
+
activate() {
|
|
87
|
+
this.selection.activate(this.clause([this.fields.map(() => 0)]));
|
|
88
|
+
}
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
function isTargetElement(groups, node) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toDataColumns } from '@uwdata/mosaic-core';
|
|
2
2
|
import { binLinear1d, isBetween } from '@uwdata/mosaic-sql';
|
|
3
|
+
import { max, sum } from 'd3';
|
|
3
4
|
import { Transient } from '../symbols.js';
|
|
4
5
|
import { binExpr } from './util/bin-expr.js';
|
|
5
6
|
import { dericheConfig, dericheConv1d } from './util/density.js';
|
|
@@ -8,9 +9,17 @@ import { grid1d } from './util/grid.js';
|
|
|
8
9
|
import { handleParam } from './util/handle-param.js';
|
|
9
10
|
import { Mark, channelOption, markQuery } from './Mark.js';
|
|
10
11
|
|
|
12
|
+
const GROUPBY = { fill: 1, stroke: 1, z: 1 };
|
|
13
|
+
|
|
11
14
|
export class Density1DMark extends Mark {
|
|
12
15
|
constructor(type, source, options) {
|
|
13
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
bins = 1024,
|
|
18
|
+
bandwidth = 20,
|
|
19
|
+
normalize = false,
|
|
20
|
+
stack = false,
|
|
21
|
+
...channels
|
|
22
|
+
} = options;
|
|
14
23
|
const dim = type.endsWith('X') ? 'y' : 'x';
|
|
15
24
|
|
|
16
25
|
super(type, source, channels, dim === 'x' ? xext : yext);
|
|
@@ -24,7 +33,17 @@ export class Density1DMark extends Mark {
|
|
|
24
33
|
/** @type {number} */
|
|
25
34
|
this.bandwidth = handleParam(bandwidth, value => {
|
|
26
35
|
this.bandwidth = value;
|
|
27
|
-
return this.
|
|
36
|
+
return this.grids ? this.convolve().update() : null;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** @type {string | boolean} */
|
|
40
|
+
this.normalize = handleParam(normalize, value => {
|
|
41
|
+
return (this.normalize = value, this.convolve().update());
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/** @type {boolean} */
|
|
45
|
+
this.stack = handleParam(stack, value => {
|
|
46
|
+
return (this.stack = value, this.update());
|
|
28
47
|
});
|
|
29
48
|
}
|
|
30
49
|
|
|
@@ -42,48 +61,76 @@ export class Density1DMark extends Mark {
|
|
|
42
61
|
const q = markQuery(channels, this.sourceTable(), [dim])
|
|
43
62
|
.where(filter.concat(isBetween(bx, extent)));
|
|
44
63
|
const v = this.channelField('weight') ? 'weight' : null;
|
|
45
|
-
|
|
64
|
+
const g = this.groupby = channels.flatMap(c => {
|
|
65
|
+
return (GROUPBY[c.channel] && c.field) ? c.as : [];
|
|
66
|
+
});
|
|
67
|
+
return binLinear1d(q, x, v, g);
|
|
46
68
|
}
|
|
47
69
|
|
|
48
70
|
queryResult(data) {
|
|
49
|
-
const
|
|
50
|
-
this.
|
|
71
|
+
const c = toDataColumns(data).columns;
|
|
72
|
+
this.grids = grid1d(this.bins, c.index, c.density, c, this.groupby);
|
|
51
73
|
return this.convolve();
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
convolve() {
|
|
55
|
-
const {
|
|
77
|
+
const {
|
|
78
|
+
bins, bandwidth, normalize, dim, grids, groupby, plot, extent: [lo, hi]
|
|
79
|
+
} = this;
|
|
80
|
+
|
|
81
|
+
const cols = grids.columns;
|
|
82
|
+
const numGrids = grids.numRows;
|
|
56
83
|
|
|
57
|
-
|
|
58
|
-
const
|
|
84
|
+
const b = this.channelField(dim).as;
|
|
85
|
+
const v = dim === 'x' ? 'y' : 'x';
|
|
59
86
|
const size = dim === 'x' ? plot.innerWidth() : plot.innerHeight();
|
|
87
|
+
const neg = cols._grid.some(grid => grid.some(v => v < 0));
|
|
60
88
|
const config = dericheConfig(bandwidth * (bins - 1) / size, neg);
|
|
61
|
-
const result = dericheConv1d(config, grid, bins);
|
|
62
89
|
|
|
63
|
-
// map smoothed grid values to sample data points
|
|
64
|
-
const v = dim === 'x' ? 'y' : 'x';
|
|
65
|
-
const b = this.channelField(dim).as;
|
|
66
90
|
const b0 = +lo;
|
|
67
91
|
const delta = (hi - b0) / (bins - 1);
|
|
68
|
-
const scale = 1 / delta;
|
|
69
92
|
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
const numRows = bins * numGrids;
|
|
94
|
+
const _b = new Float64Array(numRows);
|
|
95
|
+
const _v = new Float64Array(numRows);
|
|
96
|
+
const _g = groupby.reduce((m, name) => (m[name] = Array(numRows), m), {});
|
|
97
|
+
|
|
98
|
+
for (let k = 0, g = 0; g < numGrids; ++g) {
|
|
99
|
+
// fill in groupby values
|
|
100
|
+
groupby.forEach(name => _g[name].fill(cols[name][g], k, k + bins));
|
|
101
|
+
|
|
102
|
+
// perform smoothing, map smoothed grid values to sample data points
|
|
103
|
+
const grid = cols._grid[g];
|
|
104
|
+
const result = dericheConv1d(config, grid, bins);
|
|
105
|
+
const scale = 1 / norm(grid, result, delta, normalize);
|
|
106
|
+
for (let i = 0; i < bins; ++i, ++k) {
|
|
107
|
+
_b[k] = b0 + i * delta;
|
|
108
|
+
_v[k] = result[i] * scale;
|
|
109
|
+
}
|
|
75
110
|
}
|
|
76
|
-
this.data = { numRows: bins, columns: { [b]: _b, [v]: _v } };
|
|
77
111
|
|
|
112
|
+
this.data = { numRows, columns: { [b]: _b, [v]: _v, ..._g } };
|
|
78
113
|
return this;
|
|
79
114
|
}
|
|
80
115
|
|
|
81
116
|
plotSpecs() {
|
|
82
|
-
const { type, data: { numRows: length, columns }, channels, dim } = this;
|
|
83
|
-
|
|
117
|
+
const { type, data: { numRows: length, columns }, channels, dim, stack } = this;
|
|
118
|
+
|
|
119
|
+
// control if Plot's implicit stack transform is applied
|
|
120
|
+
// no stacking is done if x2/y2 are used instead of x/y
|
|
121
|
+
const _ = type.startsWith('area') && !stack ? '2' : '';
|
|
122
|
+
const options = dim === 'x' ? { [`y${_}`]: columns.y } : { [`x${_}`]: columns.x };
|
|
123
|
+
|
|
84
124
|
for (const c of channels) {
|
|
85
125
|
options[c.channel] = channelOption(c, columns);
|
|
86
126
|
}
|
|
87
127
|
return [{ type, data: { length }, options }];
|
|
88
128
|
}
|
|
89
129
|
}
|
|
130
|
+
|
|
131
|
+
function norm(grid, smoothed, delta, type) {
|
|
132
|
+
const value = type === true || type === 'sum' ? sum(grid)
|
|
133
|
+
: type === 'max' ? max(smoothed)
|
|
134
|
+
: delta;
|
|
135
|
+
return value || 1;
|
|
136
|
+
}
|
package/src/marks/Mark.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isParam, MosaicClient, toDataColumns } from '@uwdata/mosaic-core';
|
|
2
|
-
import { Query, SelectQuery, collectParams, column, isAggregateExpression, isColumnRef, isNode, isParamLike } from '@uwdata/mosaic-sql';
|
|
2
|
+
import { Query, SelectQuery, collectParams, column, isAggregateExpression, isColumnParam, isColumnRef, isNode, isParamLike } from '@uwdata/mosaic-sql';
|
|
3
3
|
import { isColor } from './util/is-color.js';
|
|
4
4
|
import { isConstantOption } from './util/is-constant-option.js';
|
|
5
5
|
import { isSymbol } from './util/is-symbol.js';
|
|
@@ -15,7 +15,7 @@ const isFieldObject = (channel, field) => {
|
|
|
15
15
|
const fieldEntry = (channel, field) => ({
|
|
16
16
|
channel,
|
|
17
17
|
field,
|
|
18
|
-
as: isColumnRef(field) ? field.column : channel
|
|
18
|
+
as: isColumnRef(field) && !isColumnParam(field) ? field.column : channel
|
|
19
19
|
});
|
|
20
20
|
const valueEntry = (channel, value) => ({ channel, value });
|
|
21
21
|
|
|
@@ -136,7 +136,11 @@ export class Mark extends MosaicClient {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const table = this.sourceTable();
|
|
139
|
-
return Array.from(fields, ([c, s]) => ({
|
|
139
|
+
return Array.from(fields, ([c, s]) => ({
|
|
140
|
+
table,
|
|
141
|
+
column: c,
|
|
142
|
+
stats: Array.from(s)
|
|
143
|
+
}));
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
fieldInfo(info) {
|
|
@@ -226,7 +230,7 @@ export function markQuery(channels, table, skip = []) {
|
|
|
226
230
|
if (skip.includes(channel)) continue;
|
|
227
231
|
|
|
228
232
|
if (channel === 'orderby') {
|
|
229
|
-
q.orderby(c.value);
|
|
233
|
+
q.orderby(c.value ?? field);
|
|
230
234
|
} else if (field) {
|
|
231
235
|
if (isAggregateExpression(field)) {
|
|
232
236
|
aggr = true;
|
package/src/marks/util/grid.js
CHANGED
|
@@ -24,15 +24,44 @@ export function array(size, proto = []) {
|
|
|
24
24
|
* @param {number} size The grid size.
|
|
25
25
|
* @param {Arrayish} index The grid indices for sample points.
|
|
26
26
|
* @param {Arrayish} value The sample point values.
|
|
27
|
-
* @
|
|
27
|
+
* @param {Record<string,Arrayish>} columns Named column arrays with groupby values.
|
|
28
|
+
* @param {string[]} groupby The names of columns to group by.
|
|
29
|
+
* @returns {{
|
|
30
|
+
* numRows: number;
|
|
31
|
+
* columns: { [key:string]: Arrayish }
|
|
32
|
+
* }} Named column arrays of generated grid values.
|
|
28
33
|
*/
|
|
29
|
-
export function grid1d(size, index, value) {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
export function grid1d(size, index, value, columns, groupby) {
|
|
35
|
+
const numRows = index.length;
|
|
36
|
+
const result = {};
|
|
37
|
+
const cells = [];
|
|
38
|
+
|
|
39
|
+
// if grouped, generate per-row group indices
|
|
40
|
+
if (groupby?.length) {
|
|
41
|
+
const group = new Int32Array(numRows);
|
|
42
|
+
const gvalues = groupby.map(name => columns[name]);
|
|
43
|
+
const cellMap = {};
|
|
44
|
+
for (let row = 0; row < numRows; ++row) {
|
|
45
|
+
const key = gvalues.map(group => group[row]);
|
|
46
|
+
group[row] = cellMap[key] ??= cells.push(key) - 1;
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < groupby.length; ++i) {
|
|
49
|
+
result[groupby[i]] = cells.map(cell => cell[i]);
|
|
50
|
+
}
|
|
51
|
+
const G = result._grid = cells.map(() => array(size, value));
|
|
52
|
+
for (let row = 0; row < numRows; ++row) {
|
|
53
|
+
G[group[row]][index[row]] = value[row];
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
cells.push([]); // single group
|
|
57
|
+
const [G] = result._grid = [array(size, value)]
|
|
58
|
+
for (let row = 0; row < numRows; ++row) {
|
|
59
|
+
G[index[row]] = value[row];
|
|
60
|
+
}
|
|
34
61
|
}
|
|
35
|
-
|
|
62
|
+
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
return { numRows: cells.length, columns: result };
|
|
36
65
|
}
|
|
37
66
|
|
|
38
67
|
/**
|
package/src/plot.js
CHANGED
|
@@ -10,19 +10,30 @@ const DEFAULT_ATTRIBUTES = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export class Plot {
|
|
13
|
+
/**
|
|
14
|
+
* @param {HTMLElement} [element]
|
|
15
|
+
*/
|
|
13
16
|
constructor(element) {
|
|
17
|
+
/** @type {Record<string, any>} */
|
|
14
18
|
this.attributes = { ...DEFAULT_ATTRIBUTES };
|
|
15
19
|
this.listeners = null;
|
|
16
20
|
this.interactors = [];
|
|
21
|
+
/** @type {{ legend: import('./legend.js').Legend, include: boolean }[]} */
|
|
17
22
|
this.legends = [];
|
|
23
|
+
/** @type {import('./marks/Mark.js').Mark[]} */
|
|
18
24
|
this.marks = [];
|
|
25
|
+
/** @type {Set<import('./marks/Mark.js').Mark> | null} */
|
|
19
26
|
this.markset = null;
|
|
27
|
+
/** @type {Map<import('@uwdata/mosaic-core').Param, import('./marks/Mark.js').Mark[]>} */
|
|
28
|
+
this.params = new Map;
|
|
29
|
+
/** @type {ReturnType<synchronizer>} */
|
|
30
|
+
this.synch = synchronizer();
|
|
31
|
+
|
|
32
|
+
/** @type {HTMLElement} */
|
|
20
33
|
this.element = element || document.createElement('div');
|
|
21
34
|
this.element.setAttribute('class', 'plot');
|
|
22
35
|
this.element.style.display = 'flex';
|
|
23
|
-
this.element
|
|
24
|
-
this.params = new Map;
|
|
25
|
-
this.synch = synchronizer();
|
|
36
|
+
Object.assign(this.element, { value: this });
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
margins() {
|
package/vitest.config.ts
ADDED