Skip to content

feat: interactive annotations, vector preview, named data columns#2

Merged
BenHuddart merged 8 commits into
feature/pyside-gui-editorfrom
feature/gui-annotations
Jul 4, 2026
Merged

feat: interactive annotations, vector preview, named data columns#2
BenHuddart merged 8 commits into
feature/pyside-gui-editorfrom
feature/gui-annotations

Conversation

@BenHuddart

Copy link
Copy Markdown
Owner

Summary

Follow-up to #1, stacked on feature/pyside-gui-editor. Three user-facing features plus the safety hardening that surfaced while building them.

Interactive annotations on the preview

  • Text annotations are directly manipulable on the rendered figure: drag to reposition (ghost text until the debounced re-render lands — no jumping), double-click to edit inline (Enter commits, Esc cancels, empty deletes), Delete removes, Edit ▸ Add text annotation (shortcut T) places new ones with a click
  • New Texts property tab (content, position, color, size, alignment) with loop-free two-way selection sync to the canvas
  • Powered by a calibration protocol: the preview compile embeds GLE print statements reporting each axes' exact data→page transform (parsed from the compile's own output — zero extra cost, log-axis aware, multi-subplot aware); calibration lines exist only in the temp preview copy, never in saved files
  • Recognizer now matches GLE's real stateful text semantics (set hei/just/color optional + sticky across clusters), so hand-authored annotation blocks parse fully — including TeX markup like {\it{T}}, preserved byte-exactly

Vector (SVG) preview

  • Preview renders SVG by default: crisp at any zoom with no re-compiles; View ▸ Vector preview (SVG) toggle; automatic sticky fallback to PNG if SVG compile/rendering fails
  • Found & handled empirically: GLE's default PostScript font silently truncates Cairo SVG output at exit 0 — SVG-mode previews inject a TeX font when the figure doesn't set one (exports unaffected)

Named data columns

  • Generated .dat sidecars carry a sanitized header row (x signal err instead of anonymous columns); GLE's auto-key-from-header behavior neutralized with explicit key clauses (byte-identical rendering verified)
  • The loader also recovers column names from GLE-style ! c N = name comment blocks and plain comment headers (display-only, conservative validation, unicode preserved)
  • Headers renameable in the Data dock by double-click — only for gleplot-owned sidecars

Safety hardening (adversarial review gate, 15 confirmed findings fixed)

  • Critical: import-vs-reference classification now trusts only the gleplot metadata block — the filename heuristic that could cause gleplot to silently rewrite (and corrupt) a user's own data file is removed, with an md5-unchanged regression test
  • Annotations disarm in read-only GLE-preview mode; undo mid-drag can't orphan phantom items; PNG↔SVG switches mid-drag abort instead of committing wrong-space coordinates; degenerate calibrations (non-positive log bounds) are skipped, not clamped
  • .gitignore fix: repo-wide *.gle pattern had silently excluded the shipped example (also fixed on feat: PySide6 GUI editor with native .gle format #1)

Test plan

  • Suite: 1015 → 1250 passing (+235: calibration geometry incl. real-GLE integration, SVG fallback paths, overlay interaction/no-jump/undo interplay, header round-trips, adversarial review regressions)
  • All annotation/header recognition verified against real-world hand-authored GLE file shapes (recreated synthetically in tests)
  • Manually verified on Windows 11: dragging/editing annotations on a real research figure, SVG zoom, column-name recovery, single-annotation size toggling

🤖 Generated with Claude Code

BenHuddart and others added 6 commits July 4, 2026 15:12
E1: GLE print-based calibration protocol injected into preview temp
scripts only; per-axes data/cm/pixel transforms with log-axis support
(PreviewGeometry, geometry_ready signal, never blocks rendering).

E2: SVG vector preview as default with QtSvg validation, sticky PNG
fallback and View-menu toggle; Cairo-safe font injection for SVG mode
(GLE default PostScript font silently truncates Cairo output at exit
0); frozen view_mapping contract for the annotation overlay.

E3: generated .dat sidecars carry sanitized named headers by default;
column_names round-trip through model/writer/recognizer; GLE auto-key
from headers neutralized with explicit key clauses (byte-identical
rendering verified).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
F1: AnnotationOverlay on the live preview - transparent hit-areas over
rendered text, drag with ghost and anti-jump sequencing around the
debounced re-render, mid-render drag preservation, inline editing,
add-text mode (shortcut T, Esc cancels), Delete removal, log-axis
inverse mapping, pan suspension while hovering items.

F2: TextsPanel property tab (content, position, color, size,
alignment; honest disabled state for fields GLE never renders).

MF integration: Texts tab wired with loop-free two-way selection sync
between panel and overlay, surviving re-render rebuilds, with
cross-axes retargeting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fix

G1: double-click header renaming for figure-owned sidecars with full
propagation (table, combos, series column_names, next save); external
referenced files stay read-only - gleplot never rewrites user source
data.

G2: guide sections for on-canvas annotations, preview rendering modes,
and named headers; README/rst sync; example regenerated with three
synthetic annotations, verified warning-free parse and real GLE
compile.

Fix: .gitignore ignored *.gle and data_*.dat repo-wide, so the shipped
example figure was never actually committed (docs referenced files
absent from clones). Negation patterns added; example files tracked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Data safety (critical): import-vs-reference classification now trusts
only the gleplot metadata block; the filename heuristic is removed, so
hand-written .gle files can never cause gleplot to rewrite a user data
file (reviewer repro: md5-unchanged guarantee test). Loader: all-
missing first rows are data not headers; multi-word whitespace headers
fall back to positional names with a warning. Panel ownership resolves
by sidecar basename, fixing stale propagation after Save As.

Overlay: annotations disarm in read-only GLE-preview mode (drags there
mutated the hidden document); undo/redo actions stay authoritative in
preview mode; figure_replaced aborts active interactions (no phantom
items after undo mid-drag); mapping fingerprints abort drags across
PNG/SVG switches (no wrong-space commits); degenerate calibrations
(non-positive log bounds, zero ranges) are skipped with warnings, not
clamped; Texts panel rejects non-finite coordinates and no longer
clobbers focused editors; orphaned-dict commits drop silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Text clusters: set hei/color/just are now optional and sticky across
clusters (matching GLE interpreter semantics; writer suppresses
redundant set lines to keep the fixed point); blank lines between
clusters no longer abort recognition; TeX markup in write strings
round-trips byte-exact.

Column headers: GLE-style indexed comment blocks (! c N = name) are
recovered as column names with full-coverage validation and unicode
preserved; last-comment-line fallback retained; both display-only so
vouched sidecar round-trips stay byte-identical.

Texts panel: selection changes during list rebuilds are explicitly
guarded (spurious currentRowChanged during QListWidget.clear); remove
flow guard aligned; field-isolation audit encoded as tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 4, 2026 17:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the gleplot GUI editor with (1) interactive on-canvas text annotations backed by preview calibration geometry, (2) SVG vector preview with a sticky PNG fallback, and (3) named data-column headers for generated .dat sidecars (including safe parsing/round-tripping and UI header renaming), along with related safety hardening in the recognizer and GUI mode handling.

Changes:

  • Added a calibration/geometry pipeline to map data↔page↔view coordinates and enable interactive annotation editing/selection sync in the GUI.
  • Switched live preview to SVG by default (when available) with validation-driven sticky fallback to PNG and a UI toggle.
  • Introduced named sidecar column headers, key-clause neutralization for GLE auto-key behavior, and Data dock header renaming with ownership rules + persistence.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test_text.py Verifies writer sticky-state behavior for set just/set color defaults vs non-defaults.
tests/unit/test_column_names.py Unit coverage for column-name sanitization, writer header emission, and recognizer round-trips.
tests/parser/test_recognizer.py Updates recognizer tests for sticky text semantics and conservative import/reference classification.
tests/integration/test_graphics_compilation.py Real-GLE integration proving header rows don’t change rendered PNG output.
tests/gui/test_svg_preview.py End-to-end GUI tests for SVG preview, validation, and automatic PNG fallback.
tests/gui/test_preview.py Extends GUI preview tests for render-format plumbing and geometry-reset behavior on failures.
tests/gui/test_main_window_fixes.py Regression tests for preview-mode safety hardening (overlay disabled, undo actions stay disabled).
tests/gui/test_integration.py E2E test for dragging an annotation, saving, and verifying persistence after re-parse.
tests/gui/test_header_editing.py GUI tests for Data dock header renaming rules, validation, propagation, and persistence.
tests/gui/test_geometry.py Pure-logic unit tests for calibration math + parser tolerance/validation.
tests/gui/test_data_loader.py Loader regressions for header detection (all-missing row, mismatch handling, comment-header recovery).
tests/gui/test_calibration_integration.py Real-GLE integration tests for calibration injection and geometry emission ordering.
tests/gui/test_annotation_sync.py Integration tests for Texts tab ↔ overlay selection synchronization and rebuild behavior.
src/gleplot/writer.py Implements named sidecar headers, key suppression, and sticky text-state emission behavior.
src/gleplot/parser/recognizer.py Recognizes sticky text clusters, recovers column names from sidecars, and handles key-suppression markers.
src/gleplot/gui/panels/init.py Exposes the new TextsPanel in the GUI panels package API.
src/gleplot/gui/main_window.py Wires SVG toggle, annotation overlay, Texts panel, selection sync, and preview-mode hardening.
src/gleplot/gui/geometry.py New Qt-free geometry/calibration layer and parser for gleplot-cal protocol lines.
src/gleplot/gui/data/panel.py Adds header rename UX, ownership checks, propagation into series column_names, and signals/tooltips.
src/gleplot/figure.py Passes per-series column_names through to writer methods during .gle emission.
src/gleplot/axes.py Introduces column-name sanitization + default column_names schema across series; regen defaults for old projects.
README.md Updates GUI feature list to include annotations, SVG preview, and named columns.
examples/gui/README.md Refreshes example walkthrough to include annotations and named header rows.
examples/gui/data_1.dat Adds a named header row to the example model sidecar.
examples/gui/data_0.dat Adds a named header row to the example measured-data sidecar.
examples/gui/damped_oscillation.gle Updates example .gle to include annotations and updated ranges/series emission.
examples/gui/damped_oscillation.csv Updates example CSV values to match regenerated example assets.
docs/source/gui.rst Documents SVG preview/fallback, annotations, named columns, and exports unaffected by preview substitutions.
docs/guides/GUI_EDITOR.md Adds extensive user documentation for annotations, SVG preview behavior, and header renaming/ownership rules.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +556 to +563
if not path.is_absolute():
if base is None:
continue
path = base / path
if not path.exists():
continue
if os.path.normcase(str(path.resolve())) == os.path.normcase(key):
return True
Comment thread src/gleplot/writer.py
Comment on lines 512 to +516
bar_cmd = f' bar {d_name} fill {bar_color}'
self.lines_gle.append(bar_cmd)

if column_names and not label:
self.lines_gle.append(f' {d_name} key ""')
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