Skip to content

fix(venn): synthesize implied pairwise subsets for higher-arity unions#7758

Open
mk24x7 wants to merge 2 commits into
mermaid-js:developfrom
mk24x7:bug/7656_venn-expand-implied-subsets
Open

fix(venn): synthesize implied pairwise subsets for higher-arity unions#7758
mk24x7 wants to merge 2 commits into
mermaid-js:developfrom
mk24x7:bug/7656_venn-expand-implied-subsets

Conversation

@mk24x7
Copy link
Copy Markdown

@mk24x7 mk24x7 commented May 16, 2026

📑 Summary

venn.js needs pairwise intersection sizes to make circles overlap. When the user declares only a higher-arity union (union A,B,C[label]) without the underlying pairwise unions, the circles render disjointly and there is no intersection region for the label.

Before passing subsets to venn.js in vennRenderer.ts, synthesize any missing pairwise subsets implied by higher-arity unions. User-declared subsets are untouched; only the missing pairs get filled in with a default size.

Resolves #7656.

📏 Design Decisions

  • Implemented as a pure input transformation in vennRenderer.ts (expandImpliedSubsets), keeping the parser and DB untouched. Happy to move it into vennDB.ts if you prefer that location.
  • Only synthesizes 2-element subsets, not all intermediate arities. Pairwise overlap is what venn.js's layout needs; adding 3-subsets of a 4-set would inflate the input and could produce phantom intersection labels.
  • Synthesized pairs get a default size (10/4, matching the existing 2-way default) and undefined label. The existing unstyled-intersection rendering path makes them transparent.
  • Considered patching @upsetjs/venn.js upstream, but that repo has been inactive since November 2024, and the fix is achievable cleanly on the Mermaid side.

Caveat: 4+ set unions now render as overlapping circles, but venn.js still does not produce a clean centermost text region for them, so the top-level label on a bare 4+ way union may not be visible. Strictly better than the previous disjoint layout, but a full fix would need work in venn.js itself.

📋 Tasks

Make sure you

  • 📖 have read the contribution guidelines
  • 💻 have added necessary unit/e2e tests.
  • 📓 have added documentation. Make sure MERMAID_RELEASE_VERSION is used for all new features.
  • 🦋 If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 16, 2026

🦋 Changeset detected

Latest commit: 4e51e97

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mermaid Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify
Copy link
Copy Markdown

netlify Bot commented May 16, 2026

Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit 4e51e97
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6a10268bfe6f7b00084466e5
😎 Deploy Preview https://deploy-preview-7758--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the Type: Bug / Error Something isn't working or is incorrect label May 16, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 16, 2026

Open in StackBlitz

@mermaid-js/examples

npm i https://pkg.pr.new/@mermaid-js/examples@7758

mermaid

npm i https://pkg.pr.new/mermaid@7758

@mermaid-js/layout-elk

npm i https://pkg.pr.new/@mermaid-js/layout-elk@7758

@mermaid-js/layout-tidy-tree

npm i https://pkg.pr.new/@mermaid-js/layout-tidy-tree@7758

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/@mermaid-js/mermaid-zenuml@7758

@mermaid-js/parser

npm i https://pkg.pr.new/@mermaid-js/parser@7758

@mermaid-js/tiny

npm i https://pkg.pr.new/@mermaid-js/tiny@7758

commit: 4e51e97

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

❌ Patch coverage is 0% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 3.26%. Comparing base (46e8044) to head (4e51e97).

Files with missing lines Patch % Lines
packages/mermaid/src/diagrams/venn/vennRenderer.ts 0.00% 24 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           develop   #7758      +/-   ##
==========================================
- Coverage     3.26%   3.26%   -0.01%     
==========================================
  Files          599     599              
  Lines        60839   60862      +23     
  Branches       917     917              
==========================================
  Hits          1986    1986              
- Misses       58853   58876      +23     
Flag Coverage Δ
unit 3.26% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/mermaid/src/diagrams/venn/vennRenderer.ts 0.29% <0.00%> (-0.03%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@argos-ci
Copy link
Copy Markdown

argos-ci Bot commented May 16, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 2 added May 22, 2026, 9:53 AM

Copy link
Copy Markdown
Collaborator

@knsv-bot knsv-bot left a comment

Choose a reason for hiding this comment

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

[sisyphus-bot]

Thanks for this, @mk24x7 — this is a really well-reasoned fix, and the PR description does an excellent job explaining why the bug happens (venn.js needs pairwise intersection constraints to lay out overlapping circles) rather than just what changed. 🎉

I checked this out locally and verified the behaviour:

  • expandImpliedSubsets unit tests: all pass with the fix; all 6 fail without it (function not exported) — they genuinely exercise the new logic.
  • Cypress visual tests #17/#18 are the right verification for a rendering change and cover the issue's exact 3-way input plus a 4-way case.
  • Ran a dedicated XSS/injection pass: no issues. Synthesized entries carry label: undefined and sets copied from already-parsed identifiers; they only reach venn.js geometry and D3 .text()/textContent sinks. No DOMPurify/.html()/attribute changes.

What's working well

  • 🎉 [praise] Implementing this as a pure, exported expandImpliedSubsets(subsets) transform keeps the parser/DB untouched and makes the logic trivially testable. Returning the same array reference on the no-op path (and a fresh array only when expanding) is a nice touch.
  • 🎉 [praise] Thorough unit coverage: no-op identity, all-pairwise-declared, bare 3-way, partial-preservation, bare 4-way C(N,2), and default size/label. The dedup correctly accounts for pairs shared across multiple higher-arity unions.
  • 🎉 [praise] Honest, precise PR write-up — the explicit caveat about 4+ way label visibility and the rationale for synthesizing only pairwise (not intermediate arities) show good judgement about venn.js's actual constraints.
  • 🎉 [praise] Changeset present with a clear description, correct patch bump and fix: prefix, links the issue.

Things to address

  • 🟡 [important] The render-level jsdom test renders label for 3-way union without explicit pairwise unions (issue #7656) (vennRenderer.spec.ts:~167) passes even without the fix — I verified by reverting only vennRenderer.ts and running just that test (it stays green). venn.js emits the .venn-intersection text element for any declared labeled union regardless of whether the circles actually overlap, so asserting only on label-text presence doesn't distinguish fixed from broken behaviour. The commit message describes this test as "verifying the label renders for the reporter's exact input," but as written it gives false confidence and won't catch a regression where expandImpliedSubsets stops being applied. It would be great to strengthen the assertion to verify the actual effect — e.g. that the implied pairwise subsets reach the layout / that the three circles genuinely overlap (non-degenerate pairwise intersection geometry), or assert on the .venn-intersection path for A∩B∩C having real area. The expandImpliedSubsets unit tests and the Cypress visual tests do capture the bug, so this is about closing a misleading-coverage gap, not the correctness of the fix itself.

  • 🟢 [nit] defaultPairSize = 10 / 4 (vennRenderer.ts:~39) is a magic number. It intentionally mirrors vennDB.addSubsetData's default for a 2-way subset (10 / Math.pow(identifierList.length, 2) with length 2). A short inline comment, or deriving it from the same expression / a shared constant, would prevent silent drift if that default ever changes.

  • 🟢 [nit] Docs: packages/mermaid/src/docs/syntax/venn.md only shows 2-set union examples and the PR's docs checkbox is unchecked. Nothing in the docs is now wrong (there's no obsolete "declare the pairwise unions manually" workaround note to remove), but since this fix makes a bare 3-way union actually render correctly, adding a 3-set example (the issue's "Innovation" diagram is perfect) would round it off nicely. Not blocking — this is a bug fix, not a new feature.

  • 💡 [suggestion] Out of scope / pre-existing: the sets.join('|') keying (shared with stableSetsKey/buildStyleByKey) is ambiguous if a set identifier contains a literal |. This PR neither introduces nor worsens it (synthesized entries only add geometry with label: undefined, and the security pass confirmed no injection risk), but an escaped/non-printable delimiter across the venn renderer would be a reasonable future hardening.

The fix itself is correct and well-targeted, and it's verified at both the unit and visual level — the only real ask is tightening that one render-level test so it actually guards the behaviour it claims to. Let's get this across the finish line. 🙏

mk24x7 pushed a commit to mk24x7/mermaid that referenced this pull request May 18, 2026
Address review feedback on PR mermaid-js#7758.

- vennRenderer.spec.ts: the existing jsdom render test for issue mermaid-js#7656 only asserted that the "Innovation" label appeared in the DOM, which venn.js emits for any declared labeled union regardless of whether the circles actually overlap. The test passed even with expandImpliedSubsets bypassed. Replace it with an assertion on the rendered geometry: the three implied pairwise intersections (A-B, A-C, B-C) must be present in addition to the user-declared A-B-C intersection, and the 3-way intersection path must not be the degenerate "M 0 0" placeholder venn.js emits when an area has no visible geometry. Verified to fail when the fix is bypassed.

- vennRenderer.ts: extract the synthesized-pair default size into a defaultSubsetSize(arity) helper using the same expression as vennDB.addSubsetData (10 / arity^2), and add a comment so the two defaults stay in sync.

- docs/syntax/venn.md: add a "Higher-arity unions" section with a 3-set Desirable/Feasible/Viable Innovation example, since bare 3-way unions now render correctly.
@mk24x7
Copy link
Copy Markdown
Author

mk24x7 commented May 18, 2026

Thanks for the careful review. Addressed all three points in 2d6527f49.

[important] Misleading jsdom render test. You're right that the old assertion just checked for the Innovation text node, which venn.js emits regardless of overlap. Confirmed locally that with expandImpliedSubsets bypassed the test stayed green, venn.js logged WARNING: area A,B,C not represented on screen, and the A,B,C intersection's path was rendered as the degenerate d="M 0 0" placeholder.

Replaced the assertion to verify the layout actually overlaps, via two complementary DOM-level checks:

  1. The three implied pairwise intersections (A∩B, A∩C, B∩C) appear as .venn-intersection nodes in addition to the user-declared A∩B∩C one. Without the fix only A∩B∩C is rendered.
  2. The A∩B∩C intersection's path data is not "M 0 0" and has substantive length.

Confirmed the new test fails with AssertionError: expected [ 'A|B|C' ] to deeply equal [ 'A|B', 'A|B|C', 'A|C', 'B|C' ] when the fix is bypassed. Happy to switch to a different signal (e.g. calling venn.layout directly and asserting on circle centre-to-centre distance vs radii) if you'd prefer something less coupled to venn.js's SVG output.

[nit] Magic number defaultPairSize. Replaced the literal 10 / 4 with a defaultSubsetSize(arity) helper using the same expression as vennDB.addSubsetData (10 / Math.pow(arity, 2)), plus a comment to keep them in sync. Same value, just no longer free-floating.

[nit] Docs. Added a "Higher-arity unions" section to packages/mermaid/src/docs/syntax/venn.md with the issue's exact Desirable/Feasible/Viable Innovation example, and regenerated docs/syntax/venn.md. Small thing but worth showing now that the syntax actually works.

[suggestion] sets.join('|') keying. Agreed this is a real but pre-existing ambiguity. Leaving out of scope here; happy to open a follow-up if you'd like.

Let me know if anything else needs attention.

@pbrolin47
Copy link
Copy Markdown
Collaborator

Thanks @mk24x7 for addressing the issues in commit 2d6527f! Some additional feedback, with just a minor thing :
[sisyphos-bot]

What's working well

🎉 The second commit addresses every item from the prior review cleanly and completely.

🎉 expandImpliedSubsets is a well-scoped pure function — easy to reason about, easy to test, zero DOM involvement.

🎉 The render-level test now properly guards the fix: it asserts that all three implied pairwise paths (A|B, A|C, B|C) are present and that the 3-way intersection path is
not the degenerate 'M 0 0' venn.js emits when an area has no visible geometry. The commit message explicitly confirms it was verified to fail when the fix is bypassed.

🎉 defaultSubsetSize(arity: number) => 10 / Math.pow(arity, 2) — extracting this as a named helper and aligning it with the formula in vennDB.addSubsetData means the two
defaults now stay in sync.
🎉 The ec nd comm t addresses every item from the prior review cleanly and completely.

🎉 The second commit addresses every item from the prior review cleanly and completely.

🎉 expandImpliedSubsets is a well-scoped pure function — easy to reason about, easy to test, zero DOM involvement.

🎉 The render-level test now properly guards the fix: it asserts that all three implied pairwise paths (A|B, A|C, B|C) are present and that the 3-way intersection path is not the degenerate 'M 0 0'
venn.js emits when an area has no visible geometry. The commit message explicitly confirms it was verified to fail when the fix is bypassed.

🎉 defaultSubsetSize(arity: number) => 10 / Math.pow(arity, 2) — extracting this as a named helper and aligning it with the formula in vennDB.addSubsetData means the two defaults now stay in sync.

🎉 The "Higher-arity unions" docs section is clear with a concrete Desirable/Feasible/Viable["Innovation"] example — exactly the pattern reported in the issue.


Minor observation (non-blocking)

🟢 [nit] docs/syntax/venn.md (the top-level generated folder) is committed alongside the source at packages/mermaid/src/docs/syntax/venn.md. The top-level /docs folder is auto-generated — in
principle it should only change via pnpm --filter mermaid docs:build, not manual edits. This is a common pattern in mermaid PRs so it won't block merge, but it's worth noting that the two files have
slightly different line counts (22 vs 14 additions), which suggests a minor divergence. The source file is what matters; the generated file will be overwritten on next docs build.


CI

  • All required checks pass
  • Argos: 2 added, 0 changed — only new snapshots for the 3-way and 4-way venn visual tests; no regressions in existing diagrams

@mk24x7 mk24x7 force-pushed the bug/7656_venn-expand-implied-subsets branch from 2d6527f to 2952083 Compare May 19, 2026 21:30
mk24x7 pushed a commit to mk24x7/mermaid that referenced this pull request May 19, 2026
Address review feedback on PR mermaid-js#7758.

- vennRenderer.spec.ts: the existing jsdom render test for issue mermaid-js#7656 only asserted that the "Innovation" label appeared in the DOM, which venn.js emits for any declared labeled union regardless of whether the circles actually overlap. The test passed even with expandImpliedSubsets bypassed. Replace it with an assertion on the rendered geometry: the three implied pairwise intersections (A-B, A-C, B-C) must be present in addition to the user-declared A-B-C intersection, and the 3-way intersection path must not be the degenerate "M 0 0" placeholder venn.js emits when an area has no visible geometry. Verified to fail when the fix is bypassed.

- vennRenderer.ts: extract the synthesized-pair default size into a defaultSubsetSize(arity) helper using the same expression as vennDB.addSubsetData (10 / arity^2), and add a comment so the two defaults stay in sync.

- docs/syntax/venn.md: add a "Higher-arity unions" section with a 3-set Desirable/Feasible/Viable Innovation example, since bare 3-way unions now render correctly.
@mk24x7
Copy link
Copy Markdown
Author

mk24x7 commented May 19, 2026

Thanks @pbrolin47 for taking a look!

On the docs nit: I checked locally and the generated docs/syntax/venn.md is actually in sync with the source. The line-count difference is expected because the docs build duplicates each venn-beta fenced block as a mermaid block (so Vitepress renders the diagram inline) and prepends the autogen banner, which together add the 8 lines of extra structure on top of the 14 added to the source. Running pnpm --filter mermaid docs:build followed by docs:verify on the current branch produces no diff and exits 0, so the two files are consistent.

I should have surfaced that the docs were regenerated rather than hand-edited in the original PR description though, since "manual edit to the generated folder" was a reasonable read from the outside. Will be more explicit about that next time.

Also rebased on develop to clear the BEHIND status (the two commits are now f97cc9691 and 2952083ac). Let me know if anything else needs attention.

Mukul Kumar Yadav added 2 commits May 22, 2026 15:18
The underlying venn.js layout requires explicit pairwise intersection
sizes to make circles overlap. When a user declares only a higher-arity
union (for example, `union A,B,C[label]`) without the underlying
pairwise unions, the layout has no overlap constraints, the circles
render disjointly, and there is no intersection region for the label.

Before passing subsets to venn.js, synthesize any missing pairwise
subsets implied by higher-arity unions. User-declared subsets keep
their sizes, labels, and styles; only the missing implied pairs are
filled in, with a default size.

Adds:
- expandImpliedSubsets helper in vennRenderer.ts (exported for tests)
- unit tests covering no-op, 3-way, 4-way, and partial-overlap cases
- a render-level jsdom test verifying the label renders for the
  reporter's exact input
- Cypress visual tests for bare 3-way and 4-way unions

Fixes mermaid-js#7656
Address review feedback on PR mermaid-js#7758.

- vennRenderer.spec.ts: the existing jsdom render test for issue mermaid-js#7656 only asserted that the "Innovation" label appeared in the DOM, which venn.js emits for any declared labeled union regardless of whether the circles actually overlap. The test passed even with expandImpliedSubsets bypassed. Replace it with an assertion on the rendered geometry: the three implied pairwise intersections (A-B, A-C, B-C) must be present in addition to the user-declared A-B-C intersection, and the 3-way intersection path must not be the degenerate "M 0 0" placeholder venn.js emits when an area has no visible geometry. Verified to fail when the fix is bypassed.

- vennRenderer.ts: extract the synthesized-pair default size into a defaultSubsetSize(arity) helper using the same expression as vennDB.addSubsetData (10 / arity^2), and add a comment so the two defaults stay in sync.

- docs/syntax/venn.md: add a "Higher-arity unions" section with a 3-set Desirable/Feasible/Viable Innovation example, since bare 3-way unions now render correctly.
auto-merge was automatically disabled May 22, 2026 09:48

Head branch was pushed to by a user without write access

@mk24x7 mk24x7 force-pushed the bug/7656_venn-expand-implied-subsets branch from e87bdee to 4e51e97 Compare May 22, 2026 09:48
@mk24x7
Copy link
Copy Markdown
Author

mk24x7 commented May 22, 2026

Rebased onto latest develop - cleanly merged, ready when you are. The "expected" e2e (2)-(5) checks are the detect-scope skip behavior from earlier; flagging in case branch protection needs an override.

@mk24x7
Copy link
Copy Markdown
Author

mk24x7 commented May 22, 2026

Heads up - looks like the rebase force-push automatically disabled the auto-merge @ashishjain0512 had enabled (GitHub safety policy on non-collaborator pushes). Sorry about that. Could you re-enable auto-merge or merge manually when convenient? No code change since approval - just a rebase onto latest develop for a linear history.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Graph : Venn Type: Bug / Error Something isn't working or is incorrect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Venn diagram: problem creating 3 circle union diagram + workaround

4 participants