Skip to content

feat(publish): publication-ready figure editor - #232

Merged
tsenoner merged 94 commits into
mainfrom
feat/publish-editor
May 5, 2026
Merged

feat(publish): publication-ready figure editor#232
tsenoner merged 94 commits into
mainfrom
feat/publish-editor

Conversation

@tsenoner

@tsenoner tsenoner commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add full-screen Figure Editor for creating publication-ready figures with live preview
  • 15 journal presets (Nature, Science, Cell, PNAS, PLOS) with DPI/mm/px linkage
  • Interactive overlay tools: circle (with rotation/resize), arrow, label, zoom insets
  • Legend customization: 10 positions, multi-column, free-floating with drag
  • State persistence via localStorage and parquetbundle
  • View fingerprint detection warns when overlays may be stale after projection change
  • Simplify export dropdown: promote Figure Editor as primary, add Quick Export secondary
  • DRY refactoring: deduplicate mmToPx/pxToMm, extract shared methods, add slider helper
  • 35 new tests for overlay controller and compositor (886 total, all passing)
  • Comprehensive documentation page for the Figure Editor
  • Update Python CLI guide: rewrite around protspace prepare, fix -f-a flag, add LocalMAP, annotation groups, embedder models ([DOCS] Update documentation to reflect latest changes #200)
  • Update data preparation docs with TED, Biocentral sources and LocalMAP ([DOCS] Update documentation to reflect latest changes #200)
  • Dismiss product tour in Playwright screenshot scripts
  • Regenerate all 14 documentation screenshots and GIFs ([DOCS] Update documentation to reflect latest changes #200)

tsenoner and others added 30 commits April 22, 2026 18:11
…n to state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nsets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… overflow modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to flexible preset

- Fix PNAS 1-col width (88→87 mm), PLOS widths (83→132, 173→190 mm)
- Add maxHeightMm to Cell (225), PNAS (225), PLOS (222) presets
- Replace Poster preset with Flexible (2048×1024, 300 DPI)
- Show mm dimensions in preset buttons
- Remove full-resolution checkbox (always render at export resolution)
- Remove size mode toggle (presets handle mm constraints directly)
- Dimension sliders: label+number on top, full-width slider below
- Preset mm constraint: changing DPI adjusts px, changing px adjusts DPI
- Height clamped to preset maxHeightMm when applicable
- Default preset to flexible, DPI max 1000, legend size max 100%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove overflow modes (scale/truncate/multi-column) — user controls
  columns directly via slider
- Show item counts in all columns (not just single-column)
- Add column gap spacing between count numbers and next column symbols
- Variable row heights for wrapped text — no more overlapping labels
- Center symbols and text vertically within multi-line items
- Tight legend height for overlay/free positions based on content
- Remove corner overlay and hidden legend position options
- Reserve count width in label maxTextWidth to prevent overlap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… highlight

- Replace single radius with rx/ry for proper circles on non-square canvases
- Add rotation field to CircleAnnotation
- Resize handles (4 cardinal) and rotate handle on selected circles
- Selection persists after click — handles stay visible until clicking empty space
- Sidebar items highlight on hover with corresponding canvas outline
- Fix handle positions for rotated ellipses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onstant-size handles

- Add referenceWidth to PublishState for proportional pixel property scaling
- Scale strokeWidth, arrow width/headSize, fontSize, inset border
  proportionally when image dimensions change
- Compute displayScale (canvas px / display px) so handles, highlights,
  and drag indicators appear at constant screen size regardless of
  canvas resolution
- Per-annotation property controls in sidebar: circle stroke, arrow
  stroke/head, label text/fontSize, inset border — all with slider+input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Size

- Shaft stops inside arrowhead (no gap, no poke-through)
- Arrowhead auto-sized from stroke width (headLen=4x, halfW=2x)
- Remove headSize from ArrowAnnotation and sidebar — stroke controls all
- Use butt lineCap for clean shaft-to-head transition
- Add start/end circle handles for arrow endpoint repositioning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Text renders with textAlign=center so anchor is the midpoint
- Rotation pivots around text center, not left edge
- Bounding box centered and measured at scaled font size
- Rotate handle at center-top of box, drawn in local space
- Rotation uses same atan2 formula as circle (clockwise = positive)
- Removed double-click prompt editing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Insets now render at full target-rect resolution by re-capturing the
scatterplot at up to 4x when insets exist. Also fixes a latent bug where
renderInset used plotRect offset for drawImage source coordinates,
causing wrong crops when legend is on the left or top.

Additional changes:
- Add inset resize/move handles for both source and target rects
- Remove magnification slider (zoom determined by size ratio)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use shared .publish-toggle-btn base class for toolbar and preset buttons
with blue-border-only active state matching the control-bar filter-active
pattern. Remove Escape key and dead CSS (.publish-size-mode-*).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d fingerprint

- Rename Annotation→Overlay, CircleAnnotation→CircleOverlay, etc. across
  all publish files to avoid naming clash with data annotations
- Add PublishState persistence via localStorage (survives page reload)
  and parquetbundle (survives file sharing)
- Add viewFingerprint tracking (projection + dimensionality) to detect
  when overlays may be stale after projection changes
- Add fingerprint mismatch warning banner with "Clear overlays" action
- Add "New Figure" button (clears overlays/insets, keeps layout settings)
- Remove ExportPersistenceController and all per-annotation export state
- Simplify Export dropdown: Figure Editor + Quick Export, no redundant controls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove duplicate mmToPx/pxToMm from journal-presets.ts (now imports from dimension-utils.ts)
- Extract shared _applyStateAndRebuild() from _handleReset/_handleNewFigure
- Add _renderSliderInput() helper to consolidate 10 slider+input combos
- Create CaptureablePlotElement type alias for repeated cast
- Remove dead code (void pr, unused variable)
- Add 35 new tests for overlay controller and compositor
- Add comprehensive Figure Editor documentation page
- Update exporting docs for new Figure Editor workflow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Target rect now always maintains the same pixel aspect ratio as the
source rect — during creation, corner-handle resize, and when the
source is resized. The rubber-band indicator also previews the
constrained proportions while drawing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
tsenoner added 23 commits May 5, 2026 09:40
Fill the gaps left by the previous commit: validator handling of
Inset.pointSizeScale (preserves valid, drops invalid), overlay-controller
default (newly drawn insets get 2×), and the modal's _captureInsetRenders
flow — render dims = target rect, cache on repeat call, fast-path skip
during high-frequency state churn, settle timer arming, and graceful
null-returns when the plot element doesn't expose the geometric-zoom
hooks. 9 new tests, 183 total in the publish suite.
Closes the WebGL-renderer gap that jsdom can't cover. Three Playwright
tests run against the dev server:

1. inset content reflects the source rect region — finds a high-color
   cell on the main preview, sets the inset's source there, and asserts
   the inset's interior shows the same color family (proves the
   dataDomain + getRenderInfo translation lands on the right data).

2. Dot size slider scales the rendered point coverage — colored-pixel
   count at 5× is ≥1.5× the count at 1× (proves pointSizeReference
   actually multiplies dot pixel size).

3. High-frequency target resize does not stall — 20 rAF-paced target
   resizes in <2 s, modal still mounted after settle (proves the
   fast-path skip + rAF throttle path is wired).

Wired into the existing app/tests/playwright.config.ts as a new
"figure-editor" project. Not in CI by default — run with
`pnpm test:e2e`. Full suite ~15 s.
Click an overlay or inset row in the right sidebar to select it (mirrors
canvas selection both ways via the controller). With a selection active,
Delete or Backspace removes it; Escape clears the selection. Skipped
while focus is in an editable element so typing Backspace inside the
label-text input still erases characters.
new ProtspacePublishModal() bypassed the customElements registry, so
after Vite HMR re-evaluated publish-modal.ts the import handed back a
class object that was no longer registered for the tag — the next
"Open Figure Editor" then threw "Illegal constructor" and surfaced as
an Export failed toast. createElement routes through the registry,
matching every other custom-element mount in the app.
…ick-select

Rewrites the figure-editor guide for everything added in this PR:
Photoshop-style Dimensions panel (Resample, unit toggle, aspect-lock
chain), preset constraints (width pinned, height clamped to maxHeight,
aspect-lock disabled), legend font pt/px toggle, geometric inset zoom
with per-inset Dot size slider, click-to-select sidebar items,
Delete/Backspace to remove + Escape to clear, PNG pHYs DPI metadata,
mm-accurate PDF page sizing.
fix(publish): address PR #232 review (16 findings)
@tsenoner
tsenoner merged commit eb531c9 into main May 5, 2026
4 checks passed
@tsenoner
tsenoner deleted the feat/publish-editor branch May 5, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants