Skip to content

Keep clipped features within their true map coordinates - #24

Open
paulstothard wants to merge 1 commit into
sciguy:mainfrom
paulstothard:fix/wrapped-feature-clipping
Open

paulstothard wants to merge 1 commit into
sciguy:mainfrom
paulstothard:fix/wrapped-feature-clipping

Conversation

@paulstothard

@paulstothard paulstothard commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Proposed PR: Keep clipped features within their true map coordinates

Target: sciguy/cgview-js:main
Source: paulstothard/cgview-js:fix/wrapped-feature-clipping

Summary

Intersect feature ranges with an expanded visible range before drawing them. This prevents a feature from being extended past its biological coordinates when the visible range wraps the map origin, and prevents a directional arrowhead from being placed at an artificial clipping boundary.

Why

Feature.drawRange() currently replaces a feature endpoint that is outside the visible range with visibleRange.start - 100 or visibleRange.stop + 100. That shortcut works for many ordinary views, but a wrapped visible range has two disjoint linear coordinate intervals. Treating it as one interval can extend a feature beyond its actual start or stop.

The visible symptom is most obvious after panning an off-center circular map across the origin: a preceding feature can be painted past its true endpoint and overpaint an adjacent feature. The adjacent feature has not changed its legend category or color; it is being covered by incorrect geometry from the earlier feature.

Reproduction on main:

  1. Create a circular map with adjacent features near the origin (for example, a blue feature at 12–104 bp and an orange feature at 112–178 bp on a 360 bp map).
  2. Zoom until the visible range crosses 360/1.
  3. Pan the map off center while keeping the origin-crossing range visible.
  4. The blue feature can extend beyond 104 bp and overpaint much of the orange feature.

Implementation

  • Split wrapped map ranges into one or two non-wrapping coordinate segments.
  • Expand the visible segments by the existing 100 bp drawing margin, bounded to the sequence.
  • Intersect those segments with the real feature segments.
  • Merge only overlapping or adjacent intersections.
  • Retain a clockwise arrow only on the segment containing the feature's true stop, and a counterclockwise arrow only on the segment containing its true start.
  • Keep an allocation-light constant-time path for the common case where both feature and visible range are non-wrapping.

Scope and non-goals

  • No CGView JSON, public setting, feature coordinate, legend, or track changes.
  • No change to the existing 100 bp offscreen drawing margin.
  • No change to feature ordering or colors.
  • No attempt to redesign feature decorations; the change only ensures clipping boundaries are rendered as arcs rather than false arrowheads.

Compatibility and Proksee integration

  • Existing CGView JSON files are unaffected.
  • The fix is internal to feature drawing and requires no Proksee data-model, API, or server-side change.
  • Feature coordinates, records, tracks, legend items, and event behavior are unchanged.
  • Canvas and SVG export share the same corrected segment calculation.

Tests

Automated:

  • npx jest test/Feature.test.js --runInBand — 19/19 passed.
  • npx jest --runInBand — 77/77 passed across 10 suites.
  • npm run gh-pages — production/test-page build succeeded (existing Rollup warnings only).

Regression coverage includes:

  • wrapped visible ranges;
  • ordinary non-wrapped visible ranges;
  • direct- and reverse-strand arrow placement;
  • clipped and unclipped features;
  • features outside the visible range plus margin;
  • origin-spanning features in full-map linear layout.

Manual browser verification:

  • circular Canvas rendering before and after off-center panning;
  • linear Canvas rendering, including an origin-spanning feature;
  • circular SVG export;
  • linear SVG export.

Prepared evidence files:

  • user-reproduction-before-pan-correct.png
  • user-reproduction-after-pan-overpaint.png
  • circular-panned-upstream.png
  • circular-panned-fixed.png
  • canvas-circular-linear-fixed.png
  • svg-circular-linear-fixed.png
  • index.html (standalone comparison/verification harness)

Browser evidence

Upstream main, after wrapped/off-center pan Fixed branch, same scenario
Upstream wrapped feature clipping Corrected wrapped feature clipping
Fixed Canvas: circular and linear Fixed SVG export: circular and linear
Corrected Canvas output Corrected SVG output

Performance considerations

The common non-wrapping case uses a direct constant-time intersection and does not run the wrapped-range splitting/merging logic. Wrapped ranges contain at most two feature segments and two visible segments, so the fallback has a fixed upper bound of four intersections. The change does not scan sequence bases or materialize feature data.

Risk assessment

Moderate but bounded. This replaces complicated clipping logic in a hot drawing method, so the main risks are origin wrapping, arrow placement, and linear/circular parity. Those cases have focused unit coverage and were verified in both Canvas and SVG output. No serialized or public API surface changes.

Suggested reviewer checklist

  • Reproduce the adjacent-feature overpaint on main using a wrapped visible range.
  • Confirm the preceding feature stops at its true coordinate after this change.
  • Confirm direct and reverse arrowheads appear only at biological endpoints.
  • Confirm circular and linear layouts render origin-spanning features correctly.
  • Confirm SVG export matches Canvas geometry.
  • Confirm ordinary non-wrapped maps retain expected feature appearance and performance.

This branch has not been deployed

No deployments
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.

1 participant