diff --git a/packages/mosaic/core/src/index.ts b/packages/mosaic/core/src/index.ts index 3af377c8..a349b75b 100644 --- a/packages/mosaic/core/src/index.ts +++ b/packages/mosaic/core/src/index.ts @@ -31,6 +31,7 @@ export { isArrowTable } from './util/is-arrow-table.js'; export { Synchronizer } from './util/synchronizer.js'; export { throttle } from './util/throttle.js'; export { toDataColumns } from './util/to-data-columns.js'; +export type { Arrayish, DataColumns } from './util/to-data-columns.js'; export { queryFieldInfo } from './util/field-info.js'; export { jsType } from './util/js-type.js'; export { isActivatable } from './util/is-activatable.js'; diff --git a/packages/mosaic/core/src/util/to-data-columns.ts b/packages/mosaic/core/src/util/to-data-columns.ts index 3f0861a6..8a7017c5 100644 --- a/packages/mosaic/core/src/util/to-data-columns.ts +++ b/packages/mosaic/core/src/util/to-data-columns.ts @@ -4,14 +4,14 @@ import type { Table } from '@uwdata/flechette'; /** * An Array or TypedArray */ -type Arrayish = Array | Int8Array | Uint8Array | Uint8ClampedArray +export type Arrayish = Array | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; /** * Data columns structure with either named columns or values array */ -type DataColumns = +export type DataColumns = | { numRows: number; columns: Record } | { numRows: number; values: Arrayish }; diff --git a/packages/vgplot/plot/src/interactors/Interval1D.js b/packages/vgplot/plot/src/interactors/Interval1D.js index 50825feb..eb01f11f 100644 --- a/packages/vgplot/plot/src/interactors/Interval1D.js +++ b/packages/vgplot/plot/src/interactors/Interval1D.js @@ -1,3 +1,4 @@ +/** @import { InteractorMark } from '../marks/Mark.js' */ import { clauseInterval } from '@uwdata/mosaic-core'; import { ascending, min, max } from 'd3'; import { brushGroups, brushX, brushY } from './util/brush.js'; @@ -11,6 +12,10 @@ import { sanitizeStyles } from './util/sanitize-styles.js'; * @implements {Activatable} */ export class Interval1D { + /** + * @param {InteractorMark} mark The mark to interact with. + * @param {*} options The interactor options. + */ constructor(mark, { channel, selection, diff --git a/packages/vgplot/plot/src/interactors/Nearest.js b/packages/vgplot/plot/src/interactors/Nearest.js index 0880d3f2..56faee41 100644 --- a/packages/vgplot/plot/src/interactors/Nearest.js +++ b/packages/vgplot/plot/src/interactors/Nearest.js @@ -1,4 +1,5 @@ /** @import { ClauseSource } from '@uwdata/mosaic-core' */ +/** @import { InteractorMark } from '../marks/Mark.js' */ import { clauseList, clausePoint, clausePoints, isSelection } from '@uwdata/mosaic-core'; import { select, pointer, min } from 'd3'; import { getField } from './util/get-field.js'; @@ -8,6 +9,10 @@ import { getField } from './util/get-field.js'; * @implements {Activatable} */ export class Nearest { + /** + * @param {InteractorMark} mark The mark to interact with. + * @param {*} options The interactor options. + */ constructor(mark, { selection, pointer, @@ -49,7 +54,7 @@ export class Nearest { // eslint-disable-next-line @typescript-eslint/no-this-alias const that = this; const { mark, channels, selection, maxRadius } = this; - const { data: { columns } } = mark; + const columns = mark.data && 'columns' in mark.data ? mark.data.columns : {}; const keys = channels.map(c => mark.channelField(c).as); const param = !isSelection(selection); diff --git a/packages/vgplot/plot/src/interactors/Toggle.js b/packages/vgplot/plot/src/interactors/Toggle.js index 911654a7..36afcaea 100644 --- a/packages/vgplot/plot/src/interactors/Toggle.js +++ b/packages/vgplot/plot/src/interactors/Toggle.js @@ -1,4 +1,5 @@ /** @import { ClauseSource } from '@uwdata/mosaic-core' */ +/** @import { InteractorMark } from '../marks/Mark.js' */ import { clauseList, clausePoints } from '@uwdata/mosaic-core'; import { getDatum } from './util/get-datum.js'; import { neq, neqSome } from './util/neq.js'; @@ -9,7 +10,7 @@ import { neq, neqSome } from './util/neq.js'; */ export class Toggle { /** - * @param {*} mark The mark to interact with. + * @param {InteractorMark} mark The mark to interact with. * @param {*} options The interactor options. */ constructor(mark, { @@ -58,7 +59,7 @@ export class Toggle { init(svg, selector, accessor) { const { mark, as, selection } = this; - const { data: { columns = {} } = {} } = mark; + const columns = mark.data && 'columns' in mark.data ? mark.data.columns : {}; accessor ??= target => as.map(name => columns[name][getDatum(target)]); selector ??= `[data-index="${mark.index}"]`; diff --git a/packages/vgplot/plot/src/legend.js b/packages/vgplot/plot/src/legend.js index f01763ff..a9cd9565 100644 --- a/packages/vgplot/plot/src/legend.js +++ b/packages/vgplot/plot/src/legend.js @@ -132,12 +132,20 @@ function getInteractor(legend, type) { } // generate a faux mark to pass to an interactor +/** + * @import { InteractorMark } from './marks/Mark.js' + * @returns {InteractorMark | undefined} + */ function interactorMark(legend) { const { channel, plot } = legend; const field = legend.field ?? findField(plot.marks, channel) ?? 'value'; if (field) { const f = { field }; - return { plot, channelField: c => channel === c ? f : undefined }; + return { + plot, + channelField: c => channel === c ? f : undefined, + isUnnested: () => false + }; } } diff --git a/packages/vgplot/plot/src/marks/Mark.js b/packages/vgplot/plot/src/marks/Mark.js index 25212f58..8e779604 100644 --- a/packages/vgplot/plot/src/marks/Mark.js +++ b/packages/vgplot/plot/src/marks/Mark.js @@ -1,4 +1,5 @@ /** @import { SelectQuery } from '@uwdata/mosaic-sql' */ +/** @import { DataColumns } from '@uwdata/mosaic-core' */ import { isParam, MosaicClient, queryFieldInfo, toDataColumns } from '@uwdata/mosaic-core'; import { Query, collectParams, column, isAggregateExpression, isColumnParam, isColumnRef, isNode, isParamLike, unnest } from '@uwdata/mosaic-sql'; import { isColor } from './util/is-color.js'; @@ -30,6 +31,26 @@ const valueEntry = (channel, value) => ({ channel, value }); // as opposed to a database table reference export const isDataArray = source => Array.isArray(source); +/** + * The minimal mark surface consumed by interactors, allowing marks + * and lightweight stand-ins to be used interchangeably. + * @typedef {object} InteractorMark + * @property {import('../plot.js').Plot} plot + * The plot the mark belongs to. + * @property {(channel: string, options?: { exact?: boolean }) => + * ({ field?: any, as?: any } | null | undefined)} channelField + * Look up the channel entry bound to a channel, if any. + * @property {(field: any) => boolean} isUnnested + * Whether the given field is unnested by the mark's source. + * @property {DataColumns} [data] + * Materialized column data, when available. + * @property {number} [index] + * The mark's index within the plot. + */ + +/** + * @implements {InteractorMark} + */ export class Mark extends MosaicClient { constructor(type, source, encodings, reqs = {}) { super(source?.options?.filterBy);