Problem
Visualizations are ephemeral. Once you close the browser, the computed plot is gone. There is no way to share a specific view, save cluster assignments for downstream analysis, or export results. Insights die when the session ends.
Proposed Solution
Three export capabilities:
- Export cluster assignments as CSV — Download a CSV mapping each item ID to its cluster ID and cluster name. Useful for downstream analysis in notebooks, spreadsheets, or other tools.
- Save/load plot configurations — Persist plot settings (collection, num_clusters, display fields, algorithm, etc.) as named presets so you don't have to re-configure each time.
- Screenshot — Export the current 3D view as a PNG image.
Implementation Details
Backend
New file: embedding_cluster/server/routes/export.py
GET /api/export/{job_id}/csv — Export cluster assignments
- Returns CSV with columns:
id, cluster_id, cluster_name, metadata_field_1, ...
- Uses the computed plot data (already in memory after plot computation)
- Set
Content-Disposition: attachment; filename="clusters_{collection}_{timestamp}.csv"
- Stream as CSV response using
StreamingResponse
POST /api/presets — Save a plot preset
- Request:
{ name: str, config: PlotPreset } where PlotPreset includes collection_name, num_clusters, reduction_algorithm, display_fields, etc.
- Store in a JSON file at
./presets/ directory (or SQLite for robustness)
GET /api/presets — List all saved presets
GET /api/presets/{name} — Load a specific preset
DELETE /api/presets/{name} — Delete a preset
New file: embedding_cluster/presets.py
PresetManager class handling CRUD for plot configuration presets
- File-based storage (JSON) — simple and portable
- Thread-safe read/write operations
Modify: embedding_cluster/server/models.py
- Add
PlotPreset model (all plot configuration fields)
- Add
PresetListResponse model
- Add
ExportFormat enum if supporting multiple formats later
Modify: embedding_cluster/server/app.py
- Register the export router
Modify: embedding_cluster/server/routes/plot.py
- Ensure computed plot data (cluster assignments per item) is accessible for export
- Store job results in a way that the export endpoint can retrieve them
Frontend
New file: frontend/src/components/plot/ExportToolbar.tsx
- Export button group in the plot view toolbar
- "Export CSV" button — triggers download of cluster assignments
- "Screenshot" button — captures the Three.js canvas as PNG
- "Save Preset" button — opens modal to name and save current config
- "Load Preset" dropdown — lists saved presets, applies on selection
New file: frontend/src/components/plot/PresetModal.tsx
- Modal for saving presets (name input + save button)
- Shows current configuration summary
- Validates preset name (no duplicates, no empty)
Modify: frontend/src/components/plot/ScatterPlot.tsx
- Expose canvas ref for screenshot functionality
- Screenshot via
gl.domElement.toDataURL('image/png')
Modify: frontend/src/stores/plotStore.ts
- Add
presets state and CRUD actions
- Add
exportCsv() action
- Add
takeScreenshot() action
Modify: frontend/src/api/plot.ts
- Add
exportCsv(jobId), listPresets(), savePreset(), loadPreset(), deletePreset() API functions
Screenshot Implementation
- Use Three.js
renderer.domElement.toDataURL('image/png')
- Trigger download via creating a temporary
<a> element with download attribute
- Ensure the canvas
preserveDrawingBuffer: true is set on the WebGLRenderer
Testing Requirements
Full-coverage tests are required for all new code.
Backend Tests
New file: tests/test_export.py
- Test CSV export endpoint returns valid CSV with correct columns
- Test CSV contains all items from the plot with cluster assignments
- Test CSV filename includes collection name and timestamp
- Test export with missing job_id returns 404
- Test Content-Type is
text/csv
New file: tests/test_presets.py
- Test
PresetManager CRUD operations (create, read, list, delete)
- Test saving a preset creates a JSON file
- Test loading a preset returns correct configuration
- Test listing presets returns all saved presets
- Test deleting a preset removes the file
- Test duplicate preset name handling (overwrite or error)
- Test preset name validation (no empty names, no path traversal)
- Test preset API endpoints (POST, GET, DELETE)
- Test thread safety of file operations
Frontend Tests
- ExportToolbar: all buttons render, click handlers fire correct actions
- PresetModal: name validation, save triggers API call, cancel closes modal
- Store: export and preset actions work correctly
Acceptance Criteria
Notes
- After implementation, update
README.md to document export and preset features
- Presets should be stored in a gitignored directory (e.g.,
./presets/)
- Consider adding export formats (JSON, Parquet) in a future iteration
- Screenshot should capture the current camera angle and zoom level
Problem
Visualizations are ephemeral. Once you close the browser, the computed plot is gone. There is no way to share a specific view, save cluster assignments for downstream analysis, or export results. Insights die when the session ends.
Proposed Solution
Three export capabilities:
Implementation Details
Backend
New file:
embedding_cluster/server/routes/export.pyGET /api/export/{job_id}/csv— Export cluster assignmentsid, cluster_id, cluster_name, metadata_field_1, ...Content-Disposition: attachment; filename="clusters_{collection}_{timestamp}.csv"StreamingResponsePOST /api/presets— Save a plot preset{ name: str, config: PlotPreset }where PlotPreset includes collection_name, num_clusters, reduction_algorithm, display_fields, etc../presets/directory (or SQLite for robustness)GET /api/presets— List all saved presetsGET /api/presets/{name}— Load a specific presetDELETE /api/presets/{name}— Delete a presetNew file:
embedding_cluster/presets.pyPresetManagerclass handling CRUD for plot configuration presetsModify:
embedding_cluster/server/models.pyPlotPresetmodel (all plot configuration fields)PresetListResponsemodelExportFormatenum if supporting multiple formats laterModify:
embedding_cluster/server/app.pyModify:
embedding_cluster/server/routes/plot.pyFrontend
New file:
frontend/src/components/plot/ExportToolbar.tsxNew file:
frontend/src/components/plot/PresetModal.tsxModify:
frontend/src/components/plot/ScatterPlot.tsxgl.domElement.toDataURL('image/png')Modify:
frontend/src/stores/plotStore.tspresetsstate and CRUD actionsexportCsv()actiontakeScreenshot()actionModify:
frontend/src/api/plot.tsexportCsv(jobId),listPresets(),savePreset(),loadPreset(),deletePreset()API functionsScreenshot Implementation
renderer.domElement.toDataURL('image/png')<a>element withdownloadattributepreserveDrawingBuffer: trueis set on the WebGLRendererTesting Requirements
Full-coverage tests are required for all new code.
Backend Tests
New file:
tests/test_export.pytext/csvNew file:
tests/test_presets.pyPresetManagerCRUD operations (create, read, list, delete)Frontend Tests
Acceptance Criteria
Notes
README.mdto document export and preset features./presets/)