Reference (implementation notes)#
Audience: developers and power users
Time: 5–15 minutes
What you’ll learn:
Where the figure export implementation lives
Which current architecture and authoring references define the behavior
What to check first when debugging export mismatches
Prerequisites:
Comfort reading implementation docs and browser console logs
Source and documentation references#
Figure export module README:
cellucid/assets/js/app/ui/modules/figure-export/README.mdFigure export architecture: Figure export architecture
Screenshot authoring: Docs development and style guide
Architecture (what calls what)#
Figure export is split into three layers so it stays fast and maintainable:
UI (sidebar panel)
Collects user settings and handles preview/framing interactions.cellucid/assets/js/app/ui/modules/figure-export/figure-export-ui.js
Engine (state snapshot + orchestration)
Captures view buffers/render state, builds a payload with metadata, chooses filenames, stages every renderer result, and triggers one download.cellucid/assets/js/app/ui/modules/figure-export/figure-export-engine.js
Renderers (SVG/PNG)
Turn the payload into a Blob (no DOM manipulation for SVG; a composed Canvas for PNG).cellucid/assets/js/app/ui/modules/figure-export/renderers/svg-renderer.jscellucid/assets/js/app/ui/modules/figure-export/renderers/png-renderer.js
Export worker
Runs every per-cell pass and every encode off the page’s main thread: axis bounds, the shader-accurate WebGL2 point pass (on OffscreenCanvas), the vector point markup (returned as one Blob), and PNG encoding withOffscreenCanvas.convertToBlobplus theiTXtmetadata. The page only composes, so exporting a large dataset does not freeze the tab. In a browser whose workers have no WebGL2 (WebKitGTK and WPE, WebKit bug 209809) the same handler draws the point pass on the page.cellucid/assets/js/app/ui/modules/figure-export/utils/figure-export-worker.jscellucid/assets/js/app/ui/modules/figure-export/utils/figure-export-worker-client.js
Key utilities/components:
Layout (single-view layout plus
computeGridPaneLayout(), which owns one grid cell: label row, plot rect, panel legend rect):cellucid/assets/js/app/ui/modules/figure-export/utils/layout.jsLegends, including
resolveSharedGridLegend()(may one legend stand for the whole grid?), the(hidden)entry marking, and the log-scale colorbar note:cellucid/assets/js/app/ui/modules/figure-export/components/legend-builder.jsPer-panel provenance record shared by both formats:
cellucid/assets/js/app/ui/modules/figure-export/utils/figure-provenance.jsPanel letters (
A…Z,AA…) shared by the drawn label and the metadata:cellucid/assets/js/app/ui/modules/figure-export/utils/panel-label.jsn = N selectedbadge, counted per panel:cellucid/assets/js/app/ui/modules/figure-export/utils/selection-badge.jsRender-mode fidelity gate:
cellucid/assets/js/app/ui/modules/figure-export/utils/render-mode.jsViewer-background reader and reference-layer appearance (mirrors
viewer.setBackground(); the background enum itself iscellucid/assets/js/rendering/viewer-backgrounds.js):cellucid/assets/js/app/ui/modules/figure-export/utils/viewer-background.jsReference-grid geometry, shared by SVG rules and Canvas strokes, and the entry point for either reference layer:
cellucid/assets/js/app/ui/modules/figure-export/components/reference-grid.jsStudio backdrop as vectors, from the constants and functions its shader is built from (
cellucid/assets/js/rendering/studio-backdrop.js):cellucid/assets/js/app/ui/modules/figure-export/components/studio-backdrop-export.jsModel-to-plot projection shared by both reference layers:
cellucid/assets/js/app/ui/modules/figure-export/utils/plot-projection.jsData-overlay fidelity gates (connectivity, velocity) and the disclosed viewer chrome:
cellucid/assets/js/app/ui/modules/figure-export/utils/overlay-fidelity.jsAtmospheric fog for the vector strategies:
cellucid/assets/js/app/ui/modules/figure-export/utils/fog.jsBackground-derived annotation ink:
cellucid/assets/js/app/ui/modules/figure-export/utils/figure-ink.jsDensity reduction:
cellucid/assets/js/app/ui/modules/figure-export/utils/density-reducer.jsPoint projection:
cellucid/assets/js/app/ui/modules/figure-export/utils/point-projector.jsVector point markup (written in the export worker):
cellucid/assets/js/app/ui/modules/figure-export/renderers/svg-point-markup.jsSession record of the panel’s settings:
cellucid/assets/js/app/ui/modules/figure-export/utils/session-settings.jsWebGL point rasterizer (PNG + Hybrid SVG):
cellucid/assets/js/app/ui/modules/figure-export/utils/webgl-point-rasterizer.jsPNG UTF-8 iTXt metadata:
cellucid/assets/js/app/ui/modules/figure-export/utils/png-metadata.jsDeterministic stored ZIP batches:
cellucid/assets/js/app/ui/modules/figure-export/utils/zip-archive.jsFidelity warnings dialog:
cellucid/assets/js/app/ui/modules/figure-export/components/fidelity-warning-dialog.js
Payload + metadata (what gets embedded)#
The engine constructs a payload containing:
dataset identity (name/id/source type/URLs/local folder or file name),
meta.views: one provenance record per exported panel — view id/label, that panel’s field key/kind, and that panel’s filter summary — each read from the panel’s own atomic snapshot,export options (format/size/DPI/strategy/legend/axes/reference grid/background/crop),
per-view render state + camera state snapshots.
There is no top-level view/field/filters value. A grid stamped with the active view’s field and filters would attribute one panel’s scientific state to the whole figure, which is a false claim in a published figure.
This payload is embedded into exports:
PNG: as UTF-8
iTXtchunks including a JSONCommentblob (seebuildPngTextMetadata()incellucid/assets/js/app/ui/modules/figure-export/renderers/png-renderer.js).SVG: as RDF/Dublin Core metadata plus a
cellucid:jsonfield (seebuildSvgMetadata()incellucid/assets/js/app/ui/modules/figure-export/renderers/svg-renderer.js).
Both formats build their description and JSON from the one builder in
utils/figure-provenance.js, so the SVG and PNG of a single export cannot
disagree. Figure-level claims — the PNG Color Field chunk, the SVG
cellucid:colorField tag, and the filename’s color-field segment — are written
only when unanimousFieldKey() finds every panel carrying the same field.
For one job, the engine downloads the native SVG/PNG Blob. For two or more
jobs, it validates every native Blob, writes a stable-order stored ZIP with
fixed ZIP timestamps, and downloads that one application/zip Blob. A render
or archive failure occurs before the sole download call.
Important
The metadata can include the name you gave a local folder or file
(datasetUserPath), and a dataset URL for remote sources. It is never a
filesystem path: datasetUserPath is set only from file.name or from the
first path segment of a chosen directory, because a browser never hands the page
an absolute location. Treat the name as a privacy concern when sharing figures
publicly — see What the file can and cannot reveal about you.
Known limitations (current)#
Connectivity/edges overlay is not exported (point layer only).
Only the Points render mode is exported; any other mode is a fidelity blocker, not a degraded render (
utils/render-mode.js).A multi-panel export uses one shared legend only when every panel publishes the same field and a semantically equal legend model, hidden categories included (
resolveSharedGridLegend()). Panels that disagree each get their own legend inside their cell; no colored panel is exported without a legend.A panel legend is skipped when its cell cannot host a readable one:
PANEL_LEGEND_MAX_SHARE(0.5 of the cell’s inner width or body height) minusPANEL_LEGEND_GAP(8 px) must reachPANEL_LEGEND_MIN_WIDTH(96 px, Legend: Right) orPANEL_LEGEND_MIN_HEIGHT(44 px, Legend: Bottom). These live inutils/layout.jsand are exported asLAYOUT_CONSTANTS.A shared legend beside the grid is dropped, in favor of per-panel legends, when the grid leaves under 96 px of spare width (right) or 72 px of spare height (bottom).
SVG “full vector” can be unusable for large point clouds; Hybrid/PNG are the intended practical paths.
What to capture when filing a bug (minimum repro package)#
Include enough context to reproduce without guessing:
App version/commit (or deployment date) if available
Browser + version, OS, and (if relevant) GPU model
Dataset identity:
dataset name/id,
how it was loaded (local file / server / link / Jupyter),
approximate point count and #categories in the active field
Exact steps (click-by-click) to reach the failing state
Export settings:
plot size (W×H),
format (SVG/PNG) and DPI,
strategy (Full vector, Optimized vector, or Hybrid for SVG),
include axes/legend/title/background/crop,
export all views on/off (and whether you were in grid compare)
The exported artifact attached (native SVG/PNG or the batch ZIP)
For ZIP batches: the exact ordered entry names
Console logs:
any
[FigureExport]warnings/errors,screenshot of fidelity warning dialog text if it appeared
Deterministic delivery check:
requested job count,
observed native filename or ZIP filename,
ZIP entry names for a batch.
Next steps#
Troubleshooting (figure export) (user-facing symptom mapping)
Metadata and provenance (what should be recorded/embedded)