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
4 changes: 4 additions & 0 deletions packages/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,7 @@ embedding-atlas path_to_dataset.parquet --x projection_x --y projection_y --neig

The `neighbors` column should have values in the following format: `{"ids": [id1, id2, ...], "distances": [d1, d2, ...]}`.
If this column is specified, you'll be able to see nearest neighbors for a selected point in the tool.

## Local Development

Launch Embedding Altas with a wine reviews dataset with `./start.sh` and the MNIST dataset with `./start_image.sh`.
2 changes: 2 additions & 0 deletions packages/viewer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ Start development server:
```bash
npm run dev
```

This will serve Embedding Atlas UI at http://localhost:5173. Note that the UI requires a backend server to provide data to it. You can start one via the `./start.sh` mentioned above. Without a backend server, you can still go to http://localhost:5173/#/test to view a test dataset.
96 changes: 57 additions & 39 deletions packages/viewer/src/EmbeddingAtlas.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import Spinner from "./widgets/Spinner.svelte";

import {
IconClose,
IconDarkMode,
IconDashboardLayout,
IconDownload,
Expand Down Expand Up @@ -370,19 +371,60 @@
{/if}
</div>
<!-- Right side -->
<div class="flex flex-none gap-2 items-center">
<div
class="flex flex-none gap-2 items-center pl-2 rounded-md border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-900"
>
<FilteredCount coordinator={coordinator} filter={crossFilter} table={data.table} />
<div class="flex flex-row gap-1 items-center">
<div class="flex flex-row items-center">
<button
class="flex px-2.5 mr-1 select-none items-center justify-center text-slate-500 dark:text-slate-300 rounded-full bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-600 focus-visible:outline-2 outline-blue-600 -outline-offset-1"
onclick={resetFilter}
title="Clear filters"
onclick={resetFilter}
class="rounded-md flex select-none items-center p-1.5 text-slate-400 dark:text-slate-500 focus-visible:outline-2 outline-blue-600 -outline-offset-1"
>
Clear
<IconClose class="w-5 h-5" />
</button>

{#if onExportSelection}
<PopupButton title="Export Selection">
{#snippet button({ visible, toggle })}
<button
title="Export Selection"
onclick={toggle}
class="rounded-md px-1.5 py-1.5 flex select-none items-center focus-visible:outline-2 outline-blue-600 -outline-offset-1"
class:text-slate-400={!visible}
class:dark:text-slate-500={!visible}
>
<IconExport class="w-5 h-5" />
</button>
{/snippet}
<div class="min-w-[420px] flex flex-col gap-2">
<div class="flex flex-row gap-2">
<ActionButton
icon={IconExport}
label="Export Selection"
title="Export the selected points"
class="w-48"
onClick={() => onExportSelection(currentPredicate(), exportFormat)}
/>
<Select
label="Format"
value={exportFormat}
onChange={(v) => (exportFormat = v)}
options={[
{ value: "parquet", label: "Parquet" },
{ value: "jsonl", label: "JSONL" },
{ value: "json", label: "JSON" },
{ value: "csv", label: "CSV" },
]}
/>
</div>
</div>
</PopupButton>
{/if}
</div>
</div>
<div class="flex flex-none flex-row gap-0.5">

<div class="flex flex-none flex-row gap-2">
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about gap-1? gap-2 is a bit too large IMO.

Copy link
Member Author

@domoritz domoritz Nov 21, 2025

Choose a reason for hiding this comment

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

I think it creates a nice grouping of the three buttons for changing the layout. With gap 1

Screenshot 2025-11-21 at 18 03 43 2

with gap 2

Screenshot 2025-11-21 at 18 03 36

<div class="grid grid-cols-1 grid-rows-1 justify-items-end items-center">
{#key layout}
<div transition:scale class="col-start-1 row-start-1">
Expand Down Expand Up @@ -429,41 +471,17 @@
}}
/>
{/if}
<!-- Export -->
{#if onExportSelection || onExportApplication}
<!-- Export Application -->
{#if onExportApplication}
<h4 class="text-slate-500 dark:text-slate-400 select-none">Export</h4>
<div class="flex flex-col gap-2">
{#if onExportSelection}
<div class="flex flex-row gap-2">
<ActionButton
icon={IconExport}
label="Export Selection"
title="Export the selected points"
class="w-48"
onClick={() => onExportSelection(currentPredicate(), exportFormat)}
/>
<Select
label="Format"
value={exportFormat}
onChange={(v) => (exportFormat = v)}
options={[
{ value: "parquet", label: "Parquet" },
{ value: "jsonl", label: "JSONL" },
{ value: "json", label: "JSON" },
{ value: "csv", label: "CSV" },
]}
/>
</div>
{/if}
{#if onExportApplication}
<ActionButton
icon={IconDownload}
label="Export Application"
title="Download a self-contained static web application"
class="w-48"
onClick={onExportApplication}
/>
{/if}
<ActionButton
icon={IconDownload}
label="Export Application"
title="Download a self-contained static web application"
class="w-48"
onClick={onExportApplication}
/>
</div>
{/if}
<h4 class="text-slate-500 dark:text-slate-400 select-none">About</h4>
Expand Down
9 changes: 9 additions & 0 deletions packages/viewer/src/app/Test.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@

import type { EmbeddingAtlasProps } from "../api.js";
import { initializeDatabase } from "../utils/database.js";
import { downloadBuffer } from "../utils/download.js";
import { exportMosaicSelection, type ExportFormat } from "../utils/mosaic_exporter.js";
import type { DataSource } from "./data_source.js";

export class TestDataSource implements DataSource {
private count: number;

downloadSelection: ((predicate: string | null, format: ExportFormat) => Promise<void>) | undefined = undefined;

constructor(count: number) {
this.count = count;
}
Expand Down Expand Up @@ -55,6 +59,11 @@
UPDATE ${table} SET y = y + 5 * floor(floor(var_uniform * 24 + random()) / 5);
`);

this.downloadSelection = async (predicate, format) => {
let [bytes, name] = await exportMosaicSelection(coordinator, table, predicate, format);
downloadBuffer(bytes, name);
};

return {
data: {
table: table,
Expand Down
13 changes: 11 additions & 2 deletions packages/viewer/src/widgets/PopupButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@
label?: string | null;
icon?: any | null;
anchor?: "left" | "right";
button?: Snippet<[{ visible: boolean; toggle: () => void }]>;
children?: Snippet;
}

let { title = "", label = null, icon = null, anchor = "right", children }: Props = $props();
let { title = "", label = null, icon = null, anchor = "right", children, button }: Props = $props();

let visible: boolean = $state(false);

function toggle() {
visible = !visible;
}
let container: HTMLDivElement;

function onKeyDown(e: KeyboardEvent) {
Expand Down Expand Up @@ -50,7 +55,11 @@

<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="relative" bind:this={container} onkeydown={onKeyDown}>
<ToggleButton icon={icon} title={title} label={label} bind:checked={visible} />
{#if button}
{@render button({ visible, toggle })}
{:else}
<ToggleButton icon={icon} title={title} label={label} bind:checked={visible} />
{/if}
<div
bind:this={popoverElement}
class="absolute px-3 py-3 rounded-md z-20 bg-slate-100 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 shadow-lg"
Expand Down