@uwdata/mosaic-plot 0.8.0 → 0.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-plot",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "A Mosaic-powered plotting framework based on Observable Plot.",
5
5
  "keywords": [
6
6
  "data",
@@ -10,7 +10,7 @@
10
10
  "mosaic"
11
11
  ],
12
12
  "license": "BSD-3-Clause",
13
- "author": "Jeffrey Heer (http://idl.cs.washington.edu)",
13
+ "author": "Jeffrey Heer (https://idl.uw.edu)",
14
14
  "type": "module",
15
15
  "main": "src/index.js",
16
16
  "module": "src/index.js",
@@ -28,11 +28,11 @@
28
28
  "prepublishOnly": "npm run test && npm run lint && npm run build"
29
29
  },
30
30
  "dependencies": {
31
- "@observablehq/plot": "^0.6.14",
32
- "@uwdata/mosaic-core": "^0.8.0",
33
- "@uwdata/mosaic-sql": "^0.8.0",
31
+ "@observablehq/plot": "^0.6.15",
32
+ "@uwdata/mosaic-core": "^0.10.0",
33
+ "@uwdata/mosaic-sql": "^0.10.0",
34
34
  "d3": "^7.9.0",
35
35
  "isoformat": "^0.2.1"
36
36
  },
37
- "gitHead": "a24b4c9f7dfa1c38c6af96ec17e075326c1af9b0"
37
+ "gitHead": "94fc4f0d4efc622001f6afd6714d1e9dda745be2"
38
38
  }
package/src/index.js CHANGED
@@ -8,6 +8,7 @@ export { ContourMark } from './marks/ContourMark.js';
8
8
  export { DenseLineMark } from './marks/DenseLineMark.js';
9
9
  export { Density1DMark } from './marks/Density1DMark.js';
10
10
  export { Density2DMark } from './marks/Density2DMark.js';
11
+ export { ErrorBarMark } from './marks/ErrorBarMark.js';
11
12
  export { GeoMark } from './marks/GeoMark.js';
12
13
  export { Grid2DMark } from './marks/Grid2DMark.js';
13
14
  export { HexbinMark } from './marks/HexbinMark.js';
@@ -69,7 +69,8 @@ export class Highlight {
69
69
  for (let i = 0; i < nodes.length; ++i) {
70
70
  const node = nodes[i];
71
71
  const base = values[i];
72
- const t = test(node.__data__);
72
+ const data = node.__data__;
73
+ const t = test(Array.isArray(data) ? data[0] : data);
73
74
  // TODO? handle inherited values / remove attributes
74
75
  for (let j = 0; j < channels.length; ++j) {
75
76
  const [attr, value] = channels[j];
@@ -91,9 +92,11 @@ async function predicateFunction(mark, selection) {
91
92
 
92
93
  const s = { __: and(pred) };
93
94
  const q = mark.query(filter);
94
- const p = q.groupby().length ? q.select(s) : q.$select(s);
95
+ (q.queries || [q]).forEach(q => {
96
+ q.groupby().length ? q.select(s) : q.$select(s);
97
+ });
95
98
 
96
- const data = await mark.coordinator.query(p);
99
+ const data = await mark.coordinator.query(q);
97
100
  const v = data.getChild?.('__');
98
101
  return !(data.numRows || data.length) ? (() => false)
99
102
  : v ? (i => v.get(i))
@@ -1,5 +1,5 @@
1
- import { select, min, max } from 'd3';
2
- import { isBetween } from '@uwdata/mosaic-sql';
1
+ import { clauseInterval } from '@uwdata/mosaic-core';
2
+ import { ascending, min, max, select } from 'd3';
3
3
  import { brushX, brushY } from './util/brush.js';
4
4
  import { closeTo } from './util/close-to.js';
5
5
  import { getField } from './util/get-field.js';
@@ -52,13 +52,12 @@ export class Interval1D {
52
52
 
53
53
  clause(value) {
54
54
  const { mark, pixelSize, field, scale } = this;
55
- return {
55
+ return clauseInterval(field, value, {
56
56
  source: this,
57
- schema: { type: 'interval', pixelSize, scales: [scale] },
58
57
  clients: this.peers ? mark.plot.markSet : new Set().add(mark),
59
- value,
60
- predicate: value ? isBetween(field, value) : null
61
- };
58
+ scale,
59
+ pixelSize
60
+ });
62
61
  }
63
62
 
64
63
  init(svg, root) {
@@ -69,6 +68,7 @@ export class Interval1D {
69
68
  const ry = svg.scale('y').range;
70
69
  brush.extent([[min(rx), min(ry)], [max(rx), max(ry)]]);
71
70
 
71
+ const range = this.value?.map(this.scale.apply).sort(ascending);
72
72
  const facets = select(svg).selectAll('g[aria-label="facet"]');
73
73
  root = facets.size() ? facets : select(root ?? svg);
74
74
  this.g = root
@@ -76,7 +76,7 @@ export class Interval1D {
76
76
  .attr('class', `interval-${channel}`)
77
77
  .each(patchScreenCTM)
78
78
  .call(brush)
79
- .call(brush.moveSilent, this.value?.map(this.scale.apply));
79
+ .call(brush.moveSilent, range);
80
80
 
81
81
  if (style) {
82
82
  const brushes = this.g.selectAll('rect.selection');
@@ -1,5 +1,5 @@
1
- import { select, min, max } from 'd3';
2
- import { and, isBetween } from '@uwdata/mosaic-sql';
1
+ import { clauseIntervals } from '@uwdata/mosaic-core';
2
+ import { ascending, min, max, select } from 'd3';
3
3
  import { brush } from './util/brush.js';
4
4
  import { closeTo } from './util/close-to.js';
5
5
  import { getField } from './util/get-field.js';
@@ -7,8 +7,6 @@ import { invert } from './util/invert.js';
7
7
  import { patchScreenCTM } from './util/patchScreenCTM.js';
8
8
  import { sanitizeStyles } from './util/sanitize-styles.js';
9
9
 
10
- const asc = (a, b) => a - b;
11
-
12
10
  export class Interval2D {
13
11
  constructor(mark, {
14
12
  selection,
@@ -44,8 +42,8 @@ export class Interval2D {
44
42
  let yr = undefined;
45
43
  if (extent) {
46
44
  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);
45
+ xr = [a[0], b[0]].map(v => invert(v, xscale, pixelSize)).sort(ascending);
46
+ yr = [a[1], b[1]].map(v => invert(v, yscale, pixelSize)).sort(ascending);
49
47
  }
50
48
 
51
49
  if (!closeTo(xr, value?.[0]) || !closeTo(yr, value?.[1])) {
@@ -57,15 +55,12 @@ export class Interval2D {
57
55
 
58
56
  clause(value) {
59
57
  const { mark, pixelSize, xfield, yfield, xscale, yscale } = this;
60
- return {
58
+ return clauseIntervals([xfield, yfield], value, {
61
59
  source: this,
62
- schema: { type: 'interval', pixelSize, scales: [xscale, yscale] },
63
60
  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
- };
61
+ scales: [xscale, yscale],
62
+ pixelSize
63
+ });
69
64
  }
70
65
 
71
66
  init(svg) {
@@ -92,8 +87,8 @@ export class Interval2D {
92
87
  }
93
88
 
94
89
  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);
90
+ const [x1, x2] = this.value[0].map(xscale.apply).sort(ascending);
91
+ const [y1, y2] = this.value[1].map(yscale.apply).sort(ascending);
97
92
  this.g.call(brush.moveSilent, [[x1, y1], [x2, y2]]);
98
93
  }
99
94
 
@@ -1,66 +1,112 @@
1
- import { isSelection } from '@uwdata/mosaic-core';
2
- import { eq, literal } from '@uwdata/mosaic-sql';
1
+ import { clausePoints, isSelection } from '@uwdata/mosaic-core';
3
2
  import { select, pointer } from 'd3';
4
3
  import { getField } from './util/get-field.js';
5
4
 
6
5
  export class Nearest {
7
6
  constructor(mark, {
8
7
  selection,
9
- channel,
10
- field
8
+ pointer,
9
+ channels,
10
+ fields,
11
+ maxRadius = 40
11
12
  }) {
12
13
  this.mark = mark;
13
14
  this.selection = selection;
14
15
  this.clients = new Set().add(mark);
15
- this.channel = channel;
16
- this.field = field || getField(mark, [channel]);
16
+ this.pointer = pointer;
17
+ this.channels = channels || (
18
+ pointer === 'x' ? ['x'] : pointer === 'y' ? ['y'] : ['x', 'y']
19
+ );
20
+ this.fields = fields || this.channels.map(c => getField(mark, [c]));
21
+ this.maxRadius = maxRadius;
22
+ this.valueIndex = -1;
17
23
  }
18
24
 
19
25
  clause(value) {
20
- const { clients, field } = this;
21
- const predicate = value ? eq(field, literal(value)) : null;
22
- return {
26
+ const { clients, fields } = this;
27
+ return clausePoints(fields, value ? [value] : value, {
23
28
  source: this,
24
- schema: { type: 'point' },
25
- clients,
26
- value,
27
- predicate
28
- };
29
+ clients
30
+ });
29
31
  }
30
32
 
31
33
  init(svg) {
32
34
  const that = this;
33
- const { mark, channel, selection } = this;
34
- const { data } = mark;
35
- const key = mark.channelField(channel).as;
35
+ const { mark, channels, selection, maxRadius } = this;
36
+ const { data: { columns } } = mark;
37
+ const keys = channels.map(c => mark.channelField(c).as);
38
+ const param = !isSelection(selection);
36
39
 
37
40
  const facets = select(svg).selectAll('g[aria-label="facet"]');
38
41
  const root = facets.size() ? facets : select(svg);
39
- const scale = svg.scale(channel);
40
- const param = !isSelection(selection);
41
42
 
42
- root.on('pointerdown pointermove', function(evt) {
43
- const [x, y] = pointer(evt, this);
44
- const z = findNearest(data.columns[key], scale.invert(channel === 'x' ? x : y));
45
- selection.update(param ? z : that.clause(z));
43
+ // extract x, y coordinates for data values and determine scale factors
44
+ const xscale = svg.scale('x').apply;
45
+ const yscale = svg.scale('y').apply;
46
+ const X = Array.from(columns[mark.channelField('x').as], xscale);
47
+ const Y = Array.from(columns[mark.channelField('y').as], yscale);
48
+ const sx = this.pointer === 'y' ? 0.01 : 1;
49
+ const sy = this.pointer === 'x' ? 0.01 : 1;
50
+
51
+ // find value nearest to pointer and update param or selection
52
+ // we don't pass undefined values to params, but do allow empty selections
53
+ root.on('pointerenter pointerdown pointermove', function(evt) {
54
+ const [px, py] = pointer(evt, this);
55
+ const i = findNearest(X, Y, px, py, sx, sy, maxRadius);
56
+ if (i !== this.valueIndex) {
57
+ this.valueIndex = i;
58
+ const v = i < 0 ? undefined : keys.map(k => columns[k][i]);
59
+ if (param) {
60
+ if (i > -1) selection.update(v.length > 1 ? v : v[0]);
61
+ } else {
62
+ selection.update(that.clause(v));
63
+ }
64
+ }
46
65
  });
47
66
 
67
+ // if not a selection, we're done
48
68
  if (param) return;
69
+
70
+ // clear selection upon pointer exit
71
+ root.on('pointerleave', () => {
72
+ selection.update(that.clause(undefined));
73
+ });
74
+
75
+ // trigger activation updates
49
76
  svg.addEventListener('pointerenter', evt => {
50
- if (!evt.buttons) this.selection.activate(this.clause(0));
77
+ if (!evt.buttons) {
78
+ const v = this.channels.map(() => 0);
79
+ selection.activate(this.clause(v));
80
+ }
51
81
  });
52
82
  }
53
83
  }
54
84
 
55
- function findNearest(values, value) {
56
- let dist = Infinity;
57
- let nearest;
58
-
59
- for (let i = 0; i < values.length; ++i) {
60
- const delta = Math.abs(values[i] - value);
61
- if (delta < dist) {
62
- dist = delta;
63
- nearest = values[i];
85
+ /**
86
+ * Find the nearest data point to the pointer. The nearest point
87
+ * is found via Euclidean distance, but with scale factors *sx* and
88
+ * *sy* applied to the x and y distances. For example, to prioritize
89
+ * selection along the x-axis, use *sx* = 1, *sy* = 0.01.
90
+ * @param {number[]} x Array of data point x coordinate values.
91
+ * @param {number[]} y Array of data point y coordinate values.
92
+ * @param {number} px The x coordinate of the pointer.
93
+ * @param {number} py The y coordinate of the pointer.
94
+ * @param {number} sx A scale factor for x coordinate spans.
95
+ * @param {number} sy A scale factor for y coordinate spans.
96
+ * @param {number} maxRadius The maximum pointer distance for selection.
97
+ * @returns {number} An integer index into the data array corresponding
98
+ * to the nearest data point, or -1 if no nearest point is found.
99
+ */
100
+ function findNearest(x, y, px, py, sx, sy, maxRadius) {
101
+ let dist = maxRadius * maxRadius;
102
+ let nearest = -1;
103
+ for (let i = 0; i < x.length; ++i) {
104
+ const dx = sx * (x[i] - px);
105
+ const dy = sy * (y[i] - py);
106
+ const dd = dx * dx + dy * dy;
107
+ if (dd <= dist) {
108
+ dist = dd;
109
+ nearest = i;
64
110
  }
65
111
  }
66
112
  return nearest;
@@ -1,6 +1,5 @@
1
+ import { Selection, clauseInterval } from '@uwdata/mosaic-core';
1
2
  import { select, zoom, ZoomTransform } from 'd3';
2
- import { Selection } from '@uwdata/mosaic-core';
3
- import { isBetween } from '@uwdata/mosaic-sql';
4
3
  import { getField } from './util/get-field.js';
5
4
 
6
5
  const asc = (a, b) => a - b;
@@ -49,13 +48,11 @@ export class PanZoom {
49
48
  }
50
49
 
51
50
  clause(value, field, scale) {
52
- return {
51
+ return clauseInterval(field, value, {
53
52
  source: this,
54
- schema: { type: 'interval', scales: [scale] },
55
53
  clients: this.mark.plot.markSet,
56
- value,
57
- predicate: value ? isBetween(field, value) : null
58
- };
54
+ scale
55
+ });
59
56
  }
60
57
 
61
58
  init(svg) {
@@ -1,4 +1,4 @@
1
- import { and, or, isNotDistinct, literal } from '@uwdata/mosaic-sql';
1
+ import { clausePoints } from '@uwdata/mosaic-core';
2
2
 
3
3
  export class Toggle {
4
4
  /**
@@ -14,49 +14,40 @@ export class Toggle {
14
14
  this.mark = mark;
15
15
  this.selection = selection;
16
16
  this.peers = peers;
17
- this.channels = channels.map(c => {
17
+ const fields = this.fields = [];
18
+ const as = this.as = [];
19
+ channels.forEach(c => {
18
20
  const q = c === 'color' ? ['color', 'fill', 'stroke']
19
21
  : c === 'x' ? ['x', 'x1', 'x2']
20
22
  : c === 'y' ? ['y', 'y1', 'y2']
21
23
  : [c];
22
24
  for (let i = 0; i < q.length; ++i) {
23
25
  const f = mark.channelField(q[i], { exact: true });
24
- if (f) return {
25
- field: f.field?.basis || f.field,
26
- as: f.as
27
- };
26
+ if (f) {
27
+ fields.push(f.field?.basis || f.field);
28
+ as.push(f.as);
29
+ return;
30
+ }
28
31
  }
29
32
  throw new Error(`Missing channel: ${c}`);
30
33
  });
31
34
  }
32
35
 
33
36
  clause(value) {
34
- const { channels, mark } = this;
35
- let predicate = null;
36
-
37
- if (value) {
38
- const clauses = value.map(vals => {
39
- const list = vals.map((v, i) => {
40
- return isNotDistinct(channels[i].field, literal(v));
41
- });
42
- return list.length > 1 ? and(list) : list[0];
43
- });
44
- predicate = clauses.length > 1 ? or(clauses) : clauses[0];
45
- }
46
-
47
- return {
37
+ const { fields, mark } = this;
38
+ return clausePoints(fields, value, {
48
39
  source: this,
49
- schema: { type: 'point' },
50
- clients: this.peers ? mark.plot.markSet : new Set().add(mark),
51
- value,
52
- predicate
53
- };
40
+ clients: this.peers ? mark.plot.markSet : new Set().add(mark)
41
+ });
54
42
  }
55
43
 
56
44
  init(svg, selector, accessor) {
57
- const { mark, channels, selection } = this;
45
+ const { mark, as, selection } = this;
58
46
  const { data: { columns = {} } = {} } = mark;
59
- accessor ??= target => channels.map(c => columns[c.as][target.__data__]);
47
+ accessor ??= target => as.map(name => {
48
+ const data = target.__data__;
49
+ return columns[name][Array.isArray(data) ? data[0] : data];
50
+ });
60
51
  selector ??= `[data-index="${mark.index}"]`;
61
52
  const groups = new Set(svg.querySelectorAll(selector));
62
53
 
@@ -85,7 +76,7 @@ export class Toggle {
85
76
 
86
77
  svg.addEventListener('pointerenter', evt => {
87
78
  if (evt.buttons) return;
88
- this.selection.activate(this.clause([this.channels.map(() => 0)]));
79
+ this.selection.activate(this.clause([this.fields.map(() => 0)]));
89
80
  });
90
81
  }
91
82
  }
package/src/legend.js CHANGED
@@ -10,7 +10,7 @@ export class Legend {
10
10
  constructor(channel, options) {
11
11
  const { as, field, ...rest } = options;
12
12
  this.channel = channel;
13
- this.options = { label: null, ...rest };
13
+ this.options = rest;
14
14
  this.type = null;
15
15
  this.handler = null;
16
16
  this.selection = as;
@@ -19,7 +19,7 @@ export class Legend {
19
19
 
20
20
  this.element = document.createElement('div');
21
21
  this.element.setAttribute('class', 'legend');
22
- Object.assign(this.element, { value: this });
22
+ Object.defineProperty(this.element, 'value', { value: this });
23
23
  }
24
24
 
25
25
  setPlot(plot) {
@@ -35,8 +35,13 @@ export class Legend {
35
35
 
36
36
  update() {
37
37
  if (!this.legend) return;
38
- const { value } = this.selection;
39
- const curr = value && value.length ? new Set(value.map(v => v[0])) : null;
38
+ const { selection, handler } = this;
39
+ const { single, value } = selection;
40
+
41
+ // extract currently selected values
42
+ const vals = single ? value : selection.valueFor(handler);
43
+ const curr = vals && vals.length ? new Set(vals.map(v => v[0])) : null;
44
+
40
45
  const nodes = this.legend.querySelectorAll(TOGGLE_SELECTOR);
41
46
  for (const node of nodes) {
42
47
  const selected = curr ? curr.has(node.__data__) : true;
@@ -46,10 +51,15 @@ export class Legend {
46
51
  }
47
52
 
48
53
  function createLegend(legend, svg) {
49
- const { channel, options, selection } = legend;
54
+ const { channel, plot, selection } = legend;
50
55
  const scale = svg.scale(channel);
51
56
  const type = scale.type === 'ordinal' ? SWATCH : RAMP;
52
57
 
58
+ const options = {
59
+ label: plot.getAttribute(`${channel}Label`) ?? null,
60
+ ...legend.options
61
+ };
62
+
53
63
  // labels for swatch legends are not yet supported by Plot
54
64
  // track here: https://github.com/observablehq/plot/issues/834
55
65
  // for consistent layout, adjust sizing when there is no label
@@ -103,11 +113,19 @@ function getInteractor(legend, type) {
103
113
  // otherwise instantiate an appropriate interactor
104
114
  const mark = interactorMark(legend);
105
115
  if (type === SWATCH) {
106
- legend.handler = new Toggle(mark, { selection, channels: [channel] });
116
+ legend.handler = new Toggle(mark, {
117
+ selection,
118
+ channels: [channel],
119
+ peers: false
120
+ });
107
121
  selection.addEventListener('value', () => legend.update());
108
122
  } else {
109
- const brush = { fill: 'none', stroke: 'currentColor' };
110
- legend.handler = new Interval1D(mark, { selection, channel, brush });
123
+ legend.handler = new Interval1D(mark, {
124
+ selection,
125
+ channel,
126
+ brush: { fill: 'none', stroke: 'currentColor' },
127
+ peers: false
128
+ });
111
129
  }
112
130
 
113
131
  return legend.handler;
@@ -1,3 +1,4 @@
1
+ import { toDataColumns } from '@uwdata/mosaic-core';
1
2
  import { Query, gt, isBetween, sql, sum } from '@uwdata/mosaic-sql';
2
3
  import { Transient } from '../symbols.js';
3
4
  import { binExpr } from './util/bin-expr.js';
@@ -6,7 +7,6 @@ import { extentX, extentY, xext, yext } from './util/extent.js';
6
7
  import { grid1d } from './util/grid.js';
7
8
  import { handleParam } from './util/handle-param.js';
8
9
  import { Mark, channelOption, markQuery } from './Mark.js';
9
- import { toDataColumns } from './util/to-data-columns.js';
10
10
 
11
11
  export class Density1DMark extends Mark {
12
12
  constructor(type, source, options) {
@@ -0,0 +1,50 @@
1
+ import { toDataColumns } from '@uwdata/mosaic-core';
2
+ import { avg, count, stddev } from '@uwdata/mosaic-sql';
3
+ import { erfinv } from './util/stats.js';
4
+ import { Mark, markPlotSpec, markQuery } from './Mark.js';
5
+ import { handleParam } from './util/handle-param.js';
6
+
7
+ export class ErrorBarMark extends Mark {
8
+ constructor(type, source, options) {
9
+ const dim = type.endsWith('X') ? 'y' : 'x';
10
+ const { ci = 0.95, ...channels } = options;
11
+ super(type, source, channels);
12
+ this.dim = dim;
13
+ this.field = this.channelField(dim).field;
14
+ this.channels = this.channels.filter(c => c.channel !== dim);
15
+
16
+ /** @type {number} */
17
+ this.ci = handleParam(ci, value => {
18
+ return (this.ci = value, this.update());
19
+ });
20
+ }
21
+
22
+ query(filter = []) {
23
+ const { channels, field, source: { table } } = this;
24
+ const fields = channels.concat([
25
+ { field: avg(field), as: '__avg__' },
26
+ { field: count(field), as: '__n__', },
27
+ { field: stddev(field), as: '__sd__' }
28
+ ]);
29
+ return markQuery(fields, table).where(filter);
30
+ }
31
+
32
+ queryResult(data) {
33
+ this.data = toDataColumns(data);
34
+ return this;
35
+ }
36
+
37
+ plotSpecs() {
38
+ const { type, dim, detail, data, ci, channels } = this;
39
+
40
+ // compute confidence interval channels
41
+ const p = Math.SQRT2 * erfinv(ci);
42
+ const { columns: { __avg__: u, __sd__: s, __n__: n } } = data;
43
+ const options = {
44
+ [`${dim}1`]: u.map((u, i) => u - p * s[i] / Math.sqrt(n[i])),
45
+ [`${dim}2`]: u.map((u, i) => u + p * s[i] / Math.sqrt(n[i]))
46
+ };
47
+
48
+ return markPlotSpec(type, detail, channels, data, options);
49
+ }
50
+ }
@@ -1,4 +1,5 @@
1
1
  import { interpolatorBarycentric, interpolateNearest, interpolatorRandomWalk } from '@observablehq/plot';
2
+ import { toDataColumns } from '@uwdata/mosaic-core';
2
3
  import { Query, count, isBetween, lt, lte, neq, sql, sum } from '@uwdata/mosaic-sql';
3
4
  import { Transient } from '../symbols.js';
4
5
  import { binExpr } from './util/bin-expr.js';
@@ -6,7 +7,6 @@ import { dericheConfig, dericheConv2d } from './util/density.js';
6
7
  import { extentX, extentY, xyext } from './util/extent.js';
7
8
  import { grid2d } from './util/grid.js';
8
9
  import { handleParam } from './util/handle-param.js';
9
- import { toDataColumns } from './util/to-data-columns.js';
10
10
  import { Mark } from './Mark.js';
11
11
 
12
12
  export const DENSITY = 'density';
@@ -61,7 +61,7 @@ export class Grid2DMark extends Mark {
61
61
 
62
62
  /**
63
63
  * @param {import('../plot.js').Plot} plot The plot.
64
- * @param {number} index
64
+ * @param {number} index
65
65
  */
66
66
  setPlot(plot, index) {
67
67
  const update = () => { if (this.hasFieldInfo()) this.requestUpdate(); };