Selecting a highlighted circle causes a significant performance drop when many circles overlap due to the highlight rendering path being recomputed every frame.
Root Cause:
When no circles are selected:
highlightedCircles is empty.
- In
overlaps.ts (around line 59), every overlap exits early.
drawOverlappingAreas() is never called.
When a circle is selected (e.g. A in a diagram with six fully-overlapping circles):
- Every overlap containing A is processed.
- With six circles, this is 31 overlaps (
2⁵ subsets containing A).
- Each overlap invokes
drawOverlappingAreas(), which:
- Clips the canvas once for every circle in the overlap (
ctx.clip() × overlap size).
- Draws a full-canvas fill using the background color to mask highlight regions.
As a result, rendering goes from essentially 0 highlight draw operations per frame to 100+ clip/fill operations every frame at 60 FPS.
The number of overlap regions grows exponentially:
- Total overlap regions:
2ᴺ - N - 1
- Overlaps containing a selected circle:
2ᴺ⁻¹ - 1
For six fully-overlapping circles:
| Number of circles |
Total overlaps |
Overlaps containing selected circle |
| 6 |
57 |
31 |
Why this only happens with overlapping circles
If circles do not overlap:
getOverlapsArray() returns an empty array.
- The overlap rendering loop performs no work regardless of selection state.
Likely Cause
This regression appears to have been introduced by the multiple-highlight selection feature.
Previously, highlight rendering either:
- did not require per-overlap masking, or
- handled masking outside of the per-frame rendering path.
The current implementation performs masking every frame by redrawing every affected overlap region.
Proposed Solution
Cache the rendered highlight layer using an offscreen canvas.
Instead of recomputing the highlight mask every frame:
- Render the highlight layer only when:
highlightedCircles changes.
highlightedOverlaps changes.
- Reuse the cached canvas during normal rendering.
This moves the expensive O(2ᴺ) overlap rendering work out of the hot render loop and should restore frame rate even for heavily overlapping diagrams.
Selecting a highlighted circle causes a significant performance drop when many circles overlap due to the highlight rendering path being recomputed every frame.
Root Cause:
When no circles are selected:
highlightedCirclesis empty.overlaps.ts(around line 59), every overlap exits early.drawOverlappingAreas()is never called.When a circle is selected (e.g. A in a diagram with six fully-overlapping circles):
2⁵subsets containing A).drawOverlappingAreas(), which:ctx.clip()× overlap size).As a result, rendering goes from essentially 0 highlight draw operations per frame to 100+ clip/fill operations every frame at 60 FPS.
The number of overlap regions grows exponentially:
2ᴺ - N - 12ᴺ⁻¹ - 1For six fully-overlapping circles:
Why this only happens with overlapping circles
If circles do not overlap:
getOverlapsArray()returns an empty array.Likely Cause
This regression appears to have been introduced by the multiple-highlight selection feature.
Previously, highlight rendering either:
The current implementation performs masking every frame by redrawing every affected overlap region.
Proposed Solution
Cache the rendered highlight layer using an offscreen canvas.
Instead of recomputing the highlight mask every frame:
highlightedCircleschanges.highlightedOverlapschanges.This moves the expensive
O(2ᴺ)overlap rendering work out of the hot render loop and should restore frame rate even for heavily overlapping diagrams.