@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
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { coordinator, throttle } from '@uwdata/mosaic-core';
|
|
2
|
-
import { and } from '@uwdata/mosaic-sql';
|
|
3
|
-
import { sanitizeStyles } from './util/sanitize-styles.js';
|
|
4
|
-
|
|
5
|
-
function configureMark(mark) {
|
|
6
|
-
const { channels } = mark;
|
|
7
|
-
const dims = new Set;
|
|
8
|
-
let ordered = false;
|
|
9
|
-
let aggregate = false;
|
|
10
|
-
|
|
11
|
-
for (const c of channels) {
|
|
12
|
-
const { channel, field, as } = c;
|
|
13
|
-
if (channel === 'orderby') {
|
|
14
|
-
ordered = true;
|
|
15
|
-
} else if (field) {
|
|
16
|
-
if (field.aggregate) {
|
|
17
|
-
aggregate = true;
|
|
18
|
-
} else {
|
|
19
|
-
if (dims.has(as)) continue;
|
|
20
|
-
dims.add(as);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// if orderby is defined, we're ok: nothing to do
|
|
26
|
-
// or, if there is no groupby aggregation, we're ok: nothing to do
|
|
27
|
-
// grouping may result in optimizations that change result order
|
|
28
|
-
// so we orderby the grouping dimensions to ensure stable indices
|
|
29
|
-
if (!ordered && aggregate && dims.size) {
|
|
30
|
-
mark.channels.push(({ channel: 'orderby', value: Array.from(dims) }));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return mark;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class Highlight {
|
|
37
|
-
constructor(mark, {
|
|
38
|
-
selection,
|
|
39
|
-
channels = {}
|
|
40
|
-
}) {
|
|
41
|
-
this.mark = configureMark(mark);
|
|
42
|
-
this.selection = selection;
|
|
43
|
-
const c = Object.entries(sanitizeStyles(channels));
|
|
44
|
-
this.channels = c.length ? c : [['opacity', 0.2]];
|
|
45
|
-
this.selection.addEventListener('value', throttle(() => this.update()));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
init(svg) {
|
|
49
|
-
this.svg = svg;
|
|
50
|
-
const values = this.values = [];
|
|
51
|
-
const index = this.mark.index;
|
|
52
|
-
const nodes = this.nodes = svg.querySelectorAll(`[data-index="${index}"] > *`);
|
|
53
|
-
|
|
54
|
-
const { channels } = this;
|
|
55
|
-
for (let i = 0; i < nodes.length; ++i) {
|
|
56
|
-
const node = nodes[i];
|
|
57
|
-
values.push(channels.map(c => node.getAttribute(c[0])));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return this.update();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async update() {
|
|
64
|
-
const { svg, nodes, channels, values, mark, selection } = this;
|
|
65
|
-
if (!svg) return;
|
|
66
|
-
|
|
67
|
-
const test = await predicateFunction(mark, selection);
|
|
68
|
-
|
|
69
|
-
for (let i = 0; i < nodes.length; ++i) {
|
|
70
|
-
const node = nodes[i];
|
|
71
|
-
const base = values[i];
|
|
72
|
-
const t = test(node.__data__);
|
|
73
|
-
// TODO? handle inherited values / remove attributes
|
|
74
|
-
for (let j = 0; j < channels.length; ++j) {
|
|
75
|
-
const [attr, value] = channels[j];
|
|
76
|
-
node.setAttribute(attr, t ? base[j] : value);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function predicateFunction(mark, selection) {
|
|
83
|
-
const pred = selection?.predicate(mark);
|
|
84
|
-
|
|
85
|
-
if (!pred || pred.length === 0) {
|
|
86
|
-
return () => true;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// set flag so we do not skip cross-filtered sources
|
|
90
|
-
const filter = mark.filterBy?.predicate(mark, true);
|
|
91
|
-
|
|
92
|
-
const s = { __: and(pred) };
|
|
93
|
-
const q = mark.query(filter);
|
|
94
|
-
const p = q.groupby().length ? q.select(s) : q.$select(s);
|
|
95
|
-
|
|
96
|
-
const data = await coordinator().query(p);
|
|
97
|
-
const v = data.getChild?.('__');
|
|
98
|
-
return !(data.numRows || data.length) ? (() => false)
|
|
99
|
-
: v ? (i => v.get(i))
|
|
100
|
-
: (i => data[i].__);
|
|
101
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { select, min, max } from 'd3';
|
|
2
|
-
import { isBetween } from '@uwdata/mosaic-sql';
|
|
3
|
-
import { brushX, brushY } from './util/brush.js';
|
|
4
|
-
import { closeTo } from './util/close-to.js';
|
|
5
|
-
import { getField } from './util/get-field.js';
|
|
6
|
-
import { invert } from './util/invert.js';
|
|
7
|
-
import { patchScreenCTM } from './util/patchScreenCTM.js';
|
|
8
|
-
import { sanitizeStyles } from './util/sanitize-styles.js';
|
|
9
|
-
|
|
10
|
-
export class Interval1D {
|
|
11
|
-
constructor(mark, {
|
|
12
|
-
channel,
|
|
13
|
-
selection,
|
|
14
|
-
field,
|
|
15
|
-
pixelSize = 1,
|
|
16
|
-
peers = true,
|
|
17
|
-
brush: style
|
|
18
|
-
}) {
|
|
19
|
-
this.mark = mark;
|
|
20
|
-
this.channel = channel;
|
|
21
|
-
this.pixelSize = pixelSize || 1;
|
|
22
|
-
this.selection = selection;
|
|
23
|
-
this.peers = peers;
|
|
24
|
-
this.field = field || getField(mark, [channel, channel+'1', channel+'2']);
|
|
25
|
-
this.style = style && sanitizeStyles(style);
|
|
26
|
-
this.brush = channel === 'y' ? brushY() : brushX();
|
|
27
|
-
this.brush.on('brush end', ({ selection }) => this.publish(selection));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
reset() {
|
|
31
|
-
this.value = undefined;
|
|
32
|
-
if (this.g) this.brush.reset(this.g);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
activate() {
|
|
36
|
-
this.selection.activate(this.clause(this.value || [0, 1]));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
publish(extent) {
|
|
40
|
-
let range = undefined;
|
|
41
|
-
if (extent) {
|
|
42
|
-
range = extent
|
|
43
|
-
.map(v => invert(v, this.scale, this.pixelSize))
|
|
44
|
-
.sort((a, b) => a - b);
|
|
45
|
-
}
|
|
46
|
-
if (!closeTo(range, this.value)) {
|
|
47
|
-
this.value = range;
|
|
48
|
-
this.g.call(this.brush.moveSilent, extent);
|
|
49
|
-
this.selection.update(this.clause(range));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
clause(value) {
|
|
54
|
-
const { mark, pixelSize, field, scale } = this;
|
|
55
|
-
return {
|
|
56
|
-
source: this,
|
|
57
|
-
schema: { type: 'interval', pixelSize, scales: [scale] },
|
|
58
|
-
clients: this.peers ? mark.plot.markSet : new Set().add(mark),
|
|
59
|
-
value,
|
|
60
|
-
predicate: value ? isBetween(field, value) : null
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
init(svg) {
|
|
65
|
-
const { brush, channel, style } = this;
|
|
66
|
-
this.scale = svg.scale(channel);
|
|
67
|
-
|
|
68
|
-
const rx = svg.scale('x').range;
|
|
69
|
-
const ry = svg.scale('y').range;
|
|
70
|
-
brush.extent([[min(rx), min(ry)], [max(rx), max(ry)]]);
|
|
71
|
-
|
|
72
|
-
const facets = select(svg).selectAll('g[aria-label="facet"]');
|
|
73
|
-
const root = facets.size() ? facets : select(svg);
|
|
74
|
-
this.g = root
|
|
75
|
-
.append('g')
|
|
76
|
-
.attr('class', `interval-${channel}`)
|
|
77
|
-
.each(patchScreenCTM)
|
|
78
|
-
.call(brush)
|
|
79
|
-
.call(brush.moveSilent, this.value?.map(this.scale.apply));
|
|
80
|
-
|
|
81
|
-
if (style) {
|
|
82
|
-
const brushes = this.g.selectAll('rect.selection');
|
|
83
|
-
for (const name in style) {
|
|
84
|
-
brushes.attr(name, style[name]);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
svg.addEventListener('pointerenter', () => this.activate());
|
|
89
|
-
}
|
|
90
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { select, min, max } from 'd3';
|
|
2
|
-
import { and, isBetween } from '@uwdata/mosaic-sql';
|
|
3
|
-
import { brush } from './util/brush.js';
|
|
4
|
-
import { closeTo } from './util/close-to.js';
|
|
5
|
-
import { getField } from './util/get-field.js';
|
|
6
|
-
import { invert } from './util/invert.js';
|
|
7
|
-
import { patchScreenCTM } from './util/patchScreenCTM.js';
|
|
8
|
-
import { sanitizeStyles } from './util/sanitize-styles.js';
|
|
9
|
-
|
|
10
|
-
const asc = (a, b) => a - b;
|
|
11
|
-
|
|
12
|
-
export class Interval2D {
|
|
13
|
-
constructor(mark, {
|
|
14
|
-
selection,
|
|
15
|
-
xfield,
|
|
16
|
-
yfield,
|
|
17
|
-
pixelSize = 1,
|
|
18
|
-
peers = true,
|
|
19
|
-
brush: style
|
|
20
|
-
}) {
|
|
21
|
-
this.mark = mark;
|
|
22
|
-
this.pixelSize = pixelSize || 1;
|
|
23
|
-
this.selection = selection;
|
|
24
|
-
this.peers = peers;
|
|
25
|
-
this.xfield = xfield || getField(mark, ['x', 'x1', 'x2']);
|
|
26
|
-
this.yfield = yfield || getField(mark, ['y', 'y1', 'y2']);
|
|
27
|
-
this.style = style && sanitizeStyles(style);
|
|
28
|
-
this.brush = brush();
|
|
29
|
-
this.brush.on('brush end', ({ selection }) => this.publish(selection));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
reset() {
|
|
33
|
-
this.value = undefined;
|
|
34
|
-
if (this.g) this.brush.reset(this.g);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
activate() {
|
|
38
|
-
this.selection.activate(this.clause(this.value || [[0, 1], [0, 1]]));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
publish(extent) {
|
|
42
|
-
const { value, pixelSize, xscale, yscale } = this;
|
|
43
|
-
let xr = undefined;
|
|
44
|
-
let yr = undefined;
|
|
45
|
-
if (extent) {
|
|
46
|
-
const [a, b] = extent;
|
|
47
|
-
xr = [a[0], b[0]].map(v => invert(v, xscale, pixelSize)).sort(asc);
|
|
48
|
-
yr = [a[1], b[1]].map(v => invert(v, yscale, pixelSize)).sort(asc);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!closeTo(xr, value?.[0]) || !closeTo(yr, value?.[1])) {
|
|
52
|
-
this.value = extent ? [xr, yr] : undefined;
|
|
53
|
-
this.g.call(this.brush.moveSilent, extent);
|
|
54
|
-
this.selection.update(this.clause(this.value));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
clause(value) {
|
|
59
|
-
const { mark, pixelSize, xfield, yfield, xscale, yscale } = this;
|
|
60
|
-
return {
|
|
61
|
-
source: this,
|
|
62
|
-
schema: { type: 'interval', pixelSize, scales: [xscale, yscale] },
|
|
63
|
-
clients: this.peers ? mark.plot.markSet : new Set().add(mark),
|
|
64
|
-
value,
|
|
65
|
-
predicate: value
|
|
66
|
-
? and(isBetween(xfield, value[0]), isBetween(yfield, value[1]))
|
|
67
|
-
: null
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
init(svg) {
|
|
72
|
-
const { brush, style } = this;
|
|
73
|
-
const xscale = this.xscale = svg.scale('x');
|
|
74
|
-
const yscale = this.yscale = svg.scale('y');
|
|
75
|
-
const rx = xscale.range;
|
|
76
|
-
const ry = yscale.range;
|
|
77
|
-
brush.extent([[min(rx), min(ry)], [max(rx), max(ry)]]);
|
|
78
|
-
|
|
79
|
-
const facets = select(svg).selectAll('g[aria-label="facet"]');
|
|
80
|
-
const root = facets.size() ? facets : select(svg);
|
|
81
|
-
this.g = root
|
|
82
|
-
.append('g')
|
|
83
|
-
.attr('class', `interval-xy`)
|
|
84
|
-
.each(patchScreenCTM)
|
|
85
|
-
.call(brush);
|
|
86
|
-
|
|
87
|
-
if (style) {
|
|
88
|
-
const brushes = this.g.selectAll('rect.selection');
|
|
89
|
-
for (const name in style) {
|
|
90
|
-
brushes.attr(name, style[name]);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (this.value) {
|
|
95
|
-
const [x1, x2] = this.value[0].map(xscale.apply).sort(asc);
|
|
96
|
-
const [y1, y2] = this.value[1].map(yscale.apply).sort(asc);
|
|
97
|
-
this.g.call(brush.moveSilent, [[x1, y1], [x2, y2]]);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
svg.addEventListener('pointerenter', () => this.activate());
|
|
101
|
-
}
|
|
102
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { isSelection } from '@uwdata/mosaic-core';
|
|
2
|
-
import { eq, literal } from '@uwdata/mosaic-sql';
|
|
3
|
-
import { select, pointer } from 'd3';
|
|
4
|
-
import { getField } from './util/get-field.js';
|
|
5
|
-
|
|
6
|
-
export class Nearest {
|
|
7
|
-
constructor(mark, {
|
|
8
|
-
selection,
|
|
9
|
-
channel,
|
|
10
|
-
field
|
|
11
|
-
}) {
|
|
12
|
-
this.mark = mark;
|
|
13
|
-
this.selection = selection;
|
|
14
|
-
this.clients = new Set().add(mark);
|
|
15
|
-
this.channel = channel;
|
|
16
|
-
this.field = field || getField(mark, [channel]);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
clause(value) {
|
|
20
|
-
const { clients, field } = this;
|
|
21
|
-
const predicate = value ? eq(field, literal(value)) : null;
|
|
22
|
-
return {
|
|
23
|
-
source: this,
|
|
24
|
-
schema: { type: 'point' },
|
|
25
|
-
clients,
|
|
26
|
-
value,
|
|
27
|
-
predicate
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
init(svg) {
|
|
32
|
-
const that = this;
|
|
33
|
-
const { mark, channel, selection } = this;
|
|
34
|
-
const { data } = mark;
|
|
35
|
-
const key = mark.channelField(channel).as;
|
|
36
|
-
|
|
37
|
-
const facets = select(svg).selectAll('g[aria-label="facet"]');
|
|
38
|
-
const root = facets.size() ? facets : select(svg);
|
|
39
|
-
const scale = svg.scale(channel);
|
|
40
|
-
const param = !isSelection(selection);
|
|
41
|
-
|
|
42
|
-
root.on('pointerdown pointermove', function(evt) {
|
|
43
|
-
const [x, y] = pointer(evt, this);
|
|
44
|
-
const z = findNearest(data, key, scale.invert(channel === 'x' ? x : y));
|
|
45
|
-
selection.update(param ? z : that.clause(z));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (param) return;
|
|
49
|
-
svg.addEventListener('pointerenter', () => {
|
|
50
|
-
this.selection.activate(this.clause(0));
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function findNearest(data, key, value) {
|
|
56
|
-
let dist = Infinity;
|
|
57
|
-
let v;
|
|
58
|
-
data.forEach(d => {
|
|
59
|
-
const delta = Math.abs(d[key] - value);
|
|
60
|
-
if (delta < dist) {
|
|
61
|
-
dist = delta;
|
|
62
|
-
v = d[key];
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
return v;
|
|
66
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { select, zoom, ZoomTransform } from 'd3';
|
|
2
|
-
import { Selection } from '@uwdata/mosaic-core';
|
|
3
|
-
import { isBetween } from '@uwdata/mosaic-sql';
|
|
4
|
-
import { getField } from './util/get-field.js';
|
|
5
|
-
|
|
6
|
-
const asc = (a, b) => a - b;
|
|
7
|
-
|
|
8
|
-
export class PanZoom {
|
|
9
|
-
constructor(mark, {
|
|
10
|
-
x = new Selection(),
|
|
11
|
-
y = new Selection(),
|
|
12
|
-
xfield,
|
|
13
|
-
yfield,
|
|
14
|
-
zoom = true,
|
|
15
|
-
panx = true,
|
|
16
|
-
pany = true
|
|
17
|
-
}) {
|
|
18
|
-
this.mark = mark;
|
|
19
|
-
this.xsel = x;
|
|
20
|
-
this.ysel = y;
|
|
21
|
-
this.xfield = xfield || getField(mark, ['x', 'x1', 'x2']);
|
|
22
|
-
this.yfield = yfield || getField(mark, ['y', 'y1', 'y2']);
|
|
23
|
-
this.zoom = extent(zoom, [0, Infinity], [1, 1]);
|
|
24
|
-
this.panx = this.xsel && panx;
|
|
25
|
-
this.pany = this.ysel && pany;
|
|
26
|
-
|
|
27
|
-
const { plot } = mark;
|
|
28
|
-
if (panx) {
|
|
29
|
-
this.xsel.addEventListener('value', value => {
|
|
30
|
-
if (plot.setAttribute('xDomain', value)) plot.update();
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
if (pany) {
|
|
34
|
-
this.ysel.addEventListener('value', value => {
|
|
35
|
-
if (plot.setAttribute('yDomain', value)) plot.update();
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
publish(transform) {
|
|
41
|
-
if (this.panx) {
|
|
42
|
-
const xdom = rescaleX(transform, this.xscale);
|
|
43
|
-
this.xsel.update(this.clause(xdom, this.xfield, this.xscale));
|
|
44
|
-
}
|
|
45
|
-
if (this.pany) {
|
|
46
|
-
const ydom = rescaleY(transform, this.yscale);
|
|
47
|
-
this.ysel.update(this.clause(ydom, this.yfield, this.yscale));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
clause(value, field, scale) {
|
|
52
|
-
return {
|
|
53
|
-
source: this,
|
|
54
|
-
schema: { type: 'interval', scales: [scale] },
|
|
55
|
-
clients: this.mark.plot.markSet,
|
|
56
|
-
value,
|
|
57
|
-
predicate: value ? isBetween(field, value) : null
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
init(svg) {
|
|
62
|
-
this.svg = svg;
|
|
63
|
-
if (this.initialized) return; else this.initialized = true;
|
|
64
|
-
|
|
65
|
-
const { panx, pany, mark: { plot: { element } }, xsel, ysel } = this;
|
|
66
|
-
|
|
67
|
-
this.xscale = svg.scale('x');
|
|
68
|
-
this.yscale = svg.scale('y');
|
|
69
|
-
const rx = this.xscale.range.slice().sort(asc);
|
|
70
|
-
const ry = this.yscale.range.slice().sort(asc);
|
|
71
|
-
const tx = extent(panx, [-Infinity, Infinity], rx);
|
|
72
|
-
const ty = extent(pany, [-Infinity, Infinity], ry);
|
|
73
|
-
|
|
74
|
-
const z = zoom()
|
|
75
|
-
.extent([[rx[0], ry[0]], [rx[1], ry[1]]])
|
|
76
|
-
.scaleExtent(this.zoom)
|
|
77
|
-
.translateExtent([[tx[0], ty[0]], [tx[1], ty[1]]])
|
|
78
|
-
.on('start', () => {
|
|
79
|
-
this.xscale = this.svg.scale('x');
|
|
80
|
-
this.yscale = this.svg.scale('y');
|
|
81
|
-
})
|
|
82
|
-
.on('end', () => element.__zoom = new ZoomTransform(1, 0, 0))
|
|
83
|
-
.on('zoom', ({ transform }) => this.publish(transform));
|
|
84
|
-
|
|
85
|
-
select(element).call(z);
|
|
86
|
-
|
|
87
|
-
if (panx || pany) {
|
|
88
|
-
let enter = false;
|
|
89
|
-
element.addEventListener('mouseenter', () => {
|
|
90
|
-
if (enter) return; else enter = true;
|
|
91
|
-
if (panx) {
|
|
92
|
-
const { xscale, xfield } = this;
|
|
93
|
-
xsel.activate(this.clause(xscale.domain, xfield, xscale));
|
|
94
|
-
}
|
|
95
|
-
if (pany) {
|
|
96
|
-
const { yscale, yfield } = this;
|
|
97
|
-
ysel.activate(this.clause(yscale.domain, yfield, yscale));
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
element.addEventListener('mouseleave', () => enter = false);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function extent(ext, defaultTrue, defaultFalse) {
|
|
106
|
-
return ext
|
|
107
|
-
? (Array.isArray(ext) ? ext : defaultTrue)
|
|
108
|
-
: defaultFalse;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function rescaleX(transform, scale) {
|
|
112
|
-
return scale.range
|
|
113
|
-
.map(transform.invertX, transform)
|
|
114
|
-
.map(scale.invert, scale);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function rescaleY(transform, scale) {
|
|
118
|
-
return scale.range
|
|
119
|
-
.map(transform.invertY, transform)
|
|
120
|
-
.map(scale.invert, scale);
|
|
121
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { and, or, isNotDistinct, literal } from '@uwdata/mosaic-sql';
|
|
2
|
-
|
|
3
|
-
export class Toggle {
|
|
4
|
-
constructor(mark, {
|
|
5
|
-
selection,
|
|
6
|
-
channels,
|
|
7
|
-
peers = true
|
|
8
|
-
}) {
|
|
9
|
-
this.value = null;
|
|
10
|
-
this.mark = mark;
|
|
11
|
-
this.selection = selection;
|
|
12
|
-
this.peers = peers;
|
|
13
|
-
this.channels = channels.map(c => {
|
|
14
|
-
const q = c === 'color' ? ['fill', 'stroke']
|
|
15
|
-
: c === 'x' ? ['x', 'x1', 'x2']
|
|
16
|
-
: c === 'y' ? ['y', 'y1', 'y2']
|
|
17
|
-
: [c];
|
|
18
|
-
for (let i = 0; i < q.length; ++i) {
|
|
19
|
-
const f = mark.channelField(q[i]);
|
|
20
|
-
if (f) return {
|
|
21
|
-
field: f.field?.basis || f.field,
|
|
22
|
-
as: f.as
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
throw new Error(`Missing channel: ${c}`);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
clause(value) {
|
|
30
|
-
const { channels, mark } = this;
|
|
31
|
-
let predicate = null;
|
|
32
|
-
|
|
33
|
-
if (value) {
|
|
34
|
-
const clauses = value.map(vals => {
|
|
35
|
-
const list = vals.map((v, i) => {
|
|
36
|
-
return isNotDistinct(channels[i].field, literal(v));
|
|
37
|
-
});
|
|
38
|
-
return list.length > 1 ? and(list) : list[0];
|
|
39
|
-
});
|
|
40
|
-
predicate = clauses.length > 1 ? or(clauses) : clauses[0];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
source: this,
|
|
45
|
-
schema: { type: 'point' },
|
|
46
|
-
clients: this.peers ? mark.plot.markSet : new Set().add(mark),
|
|
47
|
-
value,
|
|
48
|
-
predicate
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
init(svg, selector, accessor) {
|
|
53
|
-
const { mark, channels, selection } = this;
|
|
54
|
-
const { data } = mark;
|
|
55
|
-
accessor = accessor || (target => {
|
|
56
|
-
const datum = data[target.__data__];
|
|
57
|
-
return channels.map(c => datum[c.as]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
selector = selector || `[data-index="${mark.index}"]`;
|
|
61
|
-
const groups = new Set(svg.querySelectorAll(selector));
|
|
62
|
-
|
|
63
|
-
svg.addEventListener('pointerdown', evt => {
|
|
64
|
-
const state = selection.single ? selection.value : this.value;
|
|
65
|
-
const target = evt.target;
|
|
66
|
-
let value = null;
|
|
67
|
-
|
|
68
|
-
if (isTargetElement(groups, target)) {
|
|
69
|
-
const point = accessor(target);
|
|
70
|
-
if (evt.shiftKey && state?.length) {
|
|
71
|
-
value = state.filter(s => neq(s, point));
|
|
72
|
-
if (value.length === state.length) value.push(point);
|
|
73
|
-
} else if (state?.length === 1 && !neq(state[0], point)) {
|
|
74
|
-
value = null;
|
|
75
|
-
} else {
|
|
76
|
-
value = [point];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.value = value;
|
|
81
|
-
if (neqSome(state, value)) {
|
|
82
|
-
selection.update(this.clause(value));
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
svg.addEventListener('pointerenter', () => {
|
|
87
|
-
this.selection.activate(this.clause([this.channels.map(() => 0)]));
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isTargetElement(groups, node) {
|
|
93
|
-
return groups.has(node)
|
|
94
|
-
|| groups.has(node.parentNode)
|
|
95
|
-
|| groups.has(node.parentNode?.parentNode);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function neqSome(a, b) {
|
|
99
|
-
return (a == null || b == null)
|
|
100
|
-
? (a != null || b != null)
|
|
101
|
-
: (a.length !== b.length || a.some((x, i) => neq(x, b[i])));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function neq(a, b) {
|
|
105
|
-
const n = a.length;
|
|
106
|
-
if (b.length !== n) return true;
|
|
107
|
-
for (let i = 0; i < n; ++i) {
|
|
108
|
-
if (a[i] !== b[i]) return true;
|
|
109
|
-
}
|
|
110
|
-
return false;
|
|
111
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
brush as d3_brush, brushX as d3_brushX, brushY as d3_brushY
|
|
3
|
-
} from 'd3';
|
|
4
|
-
|
|
5
|
-
function wrap(brush) {
|
|
6
|
-
const brushOn = brush.on;
|
|
7
|
-
let enabled = true;
|
|
8
|
-
|
|
9
|
-
function silence(callback) {
|
|
10
|
-
enabled = false;
|
|
11
|
-
callback();
|
|
12
|
-
enabled = true;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
brush.reset = (...args) => {
|
|
16
|
-
silence(() => brush.clear(...args));
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
brush.moveSilent = (...args) => {
|
|
20
|
-
silence(() => brush.move(...args));
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
brush.on = (...args) => {
|
|
24
|
-
if (args.length > 1 && args[1]) {
|
|
25
|
-
// wrap callback to respect enabled flag
|
|
26
|
-
const callback = args[1];
|
|
27
|
-
args[1] = (...event) => enabled && callback(...event);
|
|
28
|
-
}
|
|
29
|
-
return brushOn(...args);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
return brush;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function brush() {
|
|
36
|
-
return wrap(d3_brush());
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function brushX() {
|
|
40
|
-
return wrap(d3_brushX());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function brushY() {
|
|
44
|
-
return wrap(d3_brushY());
|
|
45
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Patch the getScreenCTM method to memoize the last non-null
|
|
3
|
-
* result seen. This will let the method continue to function
|
|
4
|
-
* even after the node is removed from the DOM.
|
|
5
|
-
*/
|
|
6
|
-
export function patchScreenCTM() {
|
|
7
|
-
const node = this;
|
|
8
|
-
const getScreenCTM = node.getScreenCTM;
|
|
9
|
-
let memo;
|
|
10
|
-
node.getScreenCTM = () => {
|
|
11
|
-
return node.isConnected ? (memo = getScreenCTM.call(node)) : memo;
|
|
12
|
-
};
|
|
13
|
-
}
|
package/src/layout/index.js
DELETED