Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/mosaic/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 2 additions & 2 deletions packages/mosaic/core/src/util/to-data-columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import type { Table } from '@uwdata/flechette';
/**
* An Array or TypedArray
*/
type Arrayish = Array<unknown> | Int8Array | Uint8Array | Uint8ClampedArray
export type Arrayish = Array<unknown> | 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<string, Arrayish> }
| { numRows: number; values: Arrayish };

Expand Down
5 changes: 5 additions & 0 deletions packages/vgplot/plot/src/interactors/Interval1D.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion packages/vgplot/plot/src/interactors/Nearest.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
Expand Down Expand Up @@ -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);

Expand Down
5 changes: 3 additions & 2 deletions packages/vgplot/plot/src/interactors/Toggle.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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, {
Expand Down Expand Up @@ -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}"]`;
Expand Down
10 changes: 9 additions & 1 deletion packages/vgplot/plot/src/legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah this is what would've caught the side effect from #1065. good learning 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, when I saw the regression I figured this could have been caught by types so I added them and a few more that seemed useful.

@SuMayaBee SuMayaBee Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh gotcha, that's smart! Love that you covered the extra ones too 😄

*/
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
};
}
}

Expand Down
21 changes: 21 additions & 0 deletions packages/vgplot/plot/src/marks/Mark.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand Down