diff --git a/packages/viewer/src/EmbeddingAtlas.svelte b/packages/viewer/src/EmbeddingAtlas.svelte index 376434f3..530f0233 100644 --- a/packages/viewer/src/EmbeddingAtlas.svelte +++ b/packages/viewer/src/EmbeddingAtlas.svelte @@ -58,6 +58,7 @@ onExportSelection, onStateChange, cache, + highlight: highlightProp = null, }: EmbeddingAtlasProps = $props(); const { colorScheme, userColorScheme } = makeColorSchemeStore(); @@ -285,6 +286,23 @@ chartThemeStore.set(chartTheme ?? undefined); }); + // Highlight store for single point (animation + tooltip) + let highlightStore = writable(null); + + // Highlight IDs store for multiple points (overlay circles) + let highlightIdsStore = writable(null); + + // Sync external highlight prop to internal stores + $effect.pre(() => { + if (highlightProp != null && highlightProp.length > 0) { + highlightStore.set(highlightProp[0]); + highlightIdsStore.set(highlightProp); + } else { + highlightStore.set(null); + highlightIdsStore.set(null); + } + }); + let chartContext: ChartContext = { coordinator: coordinator, filter: crossFilter, @@ -299,7 +317,8 @@ searchModes: searchModes, search: doSearch, searchResult: searchResultStore, - highlight: writable(null), + highlight: highlightStore, + highlightIds: highlightIdsStore, embeddingViewConfig: embeddingViewConfig, embeddingViewLabels: embeddingViewLabels, tableCellRenderers: tableCellRenderers, diff --git a/packages/viewer/src/api.ts b/packages/viewer/src/api.ts index 71cc9aef..6f3fe597 100644 --- a/packages/viewer/src/api.ts +++ b/packages/viewer/src/api.ts @@ -87,6 +87,14 @@ export interface EmbeddingAtlasProps { /** A cache to speed up initialization of the viewer. */ cache?: Cache | null; + + /** + * An array of row IDs to highlight on the embedding view. + * When set, orange circles will be drawn at the specified points. + * The view will animate to show the first point in the array. + * Pass null or an empty array to clear the highlight. + */ + highlight?: any[] | null; } export interface EmbeddingAtlasState { diff --git a/packages/viewer/src/charts/chart.ts b/packages/viewer/src/charts/chart.ts index 8a6752d8..e04edfdd 100644 --- a/packages/viewer/src/charts/chart.ts +++ b/packages/viewer/src/charts/chart.ts @@ -89,6 +89,9 @@ export interface ChartContext { /** The current highlight point. When this changes, supported views will highlight the given point. */ highlight: Writable; + /** Multiple highlight points. When set, supported views will show overlay circles at these points. */ + highlightIds?: Readable; + /** Configuration for the embedding view. See docs for the EmbeddingView. */ embeddingViewConfig?: EmbeddingViewConfig | null; diff --git a/packages/viewer/src/charts/embedding/Embedding.svelte b/packages/viewer/src/charts/embedding/Embedding.svelte index ef2e53f8..e5b28c8f 100644 --- a/packages/viewer/src/charts/embedding/Embedding.svelte +++ b/packages/viewer/src/charts/embedding/Embedding.svelte @@ -150,6 +150,33 @@ }), ); + // Subscribe to highlightIds for programmatic multi-point highlighting + $effect.pre(() => { + if (context.highlightIds == null) return; + return context.highlightIds.subscribe(async (ids) => { + if (ids == null || ids.length == 0) { + overlayProps = null; + return; + } + let r = Array.from( + await context.coordinator.query( + SQL.Query.from(context.table) + .select({ identifier: SQL.column(context.id), x: SQL.column(spec.data.x), y: SQL.column(spec.data.y) }) + .where( + SQL.isIn( + context.id, + ids.map((x) => SQL.literal(x)), + ), + ), + ), + ) as DataPoint[]; + overlayProps = { + center: null, + points: r, + }; + }); + }); + async function animateToPoint(identifier: RowID): Promise { let defaultScale = await context.cache.value(`embedding/default-viewport-scale/${spec.data.x},${spec.data.y}`, () => defaultViewportScale(context.coordinator, context.table, spec.data.x, spec.data.y),