From 70fb1c2f2b4d6c5c1ffcc307cd151d5b56f61408 Mon Sep 17 00:00:00 2001 From: szxc Date: Wed, 14 May 2025 21:16:37 +0800 Subject: [PATCH 01/10] docs: update changlog of rush --- .../vlayouts/feat-venn_2025-05-14-13-16.json | 11 ++++++++ .../vutils/feat-venn_2025-05-14-13-16.json | 11 ++++++++ packages/vlayouts/__tests__/venn/venn.test.ts | 28 +++++++++++++++++++ packages/vlayouts/src/venn/utils/label.ts | 4 +++ packages/vlayouts/src/venn/venn.ts | 15 ++++++++++ .../vutils/src/geo/circle-intersection.ts | 11 ++++++++ 6 files changed, 80 insertions(+) create mode 100644 common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json create mode 100644 common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json diff --git a/common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json b/common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json new file mode 100644 index 00000000..fb5446b3 --- /dev/null +++ b/common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Merge pull request #237 from VisActor/release/1.0.5\n\n[Auto release] release 1.0.5\n", + "type": "none", + "packageName": "@visactor/vlayouts" + } + ], + "packageName": "@visactor/vlayouts", + "email": "shuzhuxvchuang@163.com" +} \ No newline at end of file diff --git a/common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json b/common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json new file mode 100644 index 00000000..3e1d7ad2 --- /dev/null +++ b/common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Merge pull request #237 from VisActor/release/1.0.5\n\n[Auto release] release 1.0.5\n", + "type": "none", + "packageName": "@visactor/vutils" + } + ], + "packageName": "@visactor/vutils", + "email": "shuzhuxvchuang@163.com" +} \ No newline at end of file diff --git a/packages/vlayouts/__tests__/venn/venn.test.ts b/packages/vlayouts/__tests__/venn/venn.test.ts index 355dd7b7..6e58c092 100644 --- a/packages/vlayouts/__tests__/venn/venn.test.ts +++ b/packages/vlayouts/__tests__/venn/venn.test.ts @@ -60,3 +60,31 @@ test('Path transform of 3 element venn', async () => { expect(circles[2].x).toBeCloseTo((result[2] as IVennCircleDatum).x, 0); expect(circles[2].y).toBeCloseTo((result[2] as IVennCircleDatum).y, 0); }); + +const data2 = [ + { sets: [], size: 0, label: 'none' }, + { sets: ['A'], size: 12, label: 'A' }, + { sets: ['B'], size: 12, label: 'B' }, + { sets: ['A', 'B'], size: 4, label: 'A,B', stroke: 'red' } +]; + +test('Data transform of 3 element venn with empty set', async () => { + const result = await transform( + { + x0: 0, + y0: 0, + x1: 500, + y1: 500 + }, + data2 + ); + + expect(result.length).toEqual(4); + + expect(result[0].type).toEqual('circle'); + expect(result[3].type).toEqual('overlap'); + + expect((result[0] as IVennCircleDatum).radius).toBeCloseTo(250, 0); + expect((result[0] as IVennCircleDatum).x).toBeCloseTo(250, 0); + expect((result[0] as IVennCircleDatum).y).toBeCloseTo(250, 0); +}); diff --git a/packages/vlayouts/src/venn/utils/label.ts b/packages/vlayouts/src/venn/utils/label.ts index 8d7f6fa9..c6449c57 100644 --- a/packages/vlayouts/src/venn/utils/label.ts +++ b/packages/vlayouts/src/venn/utils/label.ts @@ -19,6 +19,10 @@ export function computeTextCenters( const overlapped = getOverlappingCircles(circles); for (let i = 0; i < areas.length; ++i) { const area = areas[i].sets; + if (!area || area.length === 0) { + ret[''] = { x: 0, y: 0 }; + continue; + } const areaIds: Record = {}; const exclude: Record = {}; for (let j = 0; j < area.length; ++j) { diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index 7b4d8b75..d2083d4e 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -45,6 +45,21 @@ export const transform = ( const data = upstreamData.map(area => { const sets = array(area[setField]); + if (!sets || sets.length === 0) { + return { + ...area, + datum: area, + sets, + key: '', + size: area[valueField], + labelX: undefined, + labelY: undefined, + type: 'circle', + x: x0 + (x1 - x0) / 2, + y: y0 + (y1 - y0) / 2, + radius: Math.max(x1 - x0, y1 - y0) / 2 + } as IVennCircleDatum; + } const key = sets.toString(); const textCenter = textCenters[key]; const basicDatum = { diff --git a/packages/vutils/src/geo/circle-intersection.ts b/packages/vutils/src/geo/circle-intersection.ts index bf9a319c..47a15dee 100644 --- a/packages/vutils/src/geo/circle-intersection.ts +++ b/packages/vutils/src/geo/circle-intersection.ts @@ -16,6 +16,17 @@ import type { ICircleArc, ICircle, IIntersectPoint, IOverlapAreaStats } from './ /** Returns the intersection area of a bunch of circles (where each circle is an object having an x,y and radius property) */ export function intersectionArea(circles: ICircle[], stats?: IOverlapAreaStats) { + if (!circles || !circles.length) { + if (stats) { + stats.area = 0; + stats.arcArea = 0; + stats.polygonArea = 0; + stats.arcs = []; + stats.innerPoints = []; + stats.intersectionPoints = []; + } + return 0; + } // get all the intersection points of the circles const intersectionPoints = getIntersectionPoints(circles); From 5a931d882893c9cccbd907870eb9c37001328b57 Mon Sep 17 00:00:00 2001 From: szxc Date: Thu, 15 May 2025 11:24:16 +0800 Subject: [PATCH 02/10] docs: update changlog of rush --- .../vlayouts/feat-venn_2025-05-15-03-24.json | 11 +++++++++++ .../@visactor/vutils/feat-venn_2025-05-15-03-24.json | 11 +++++++++++ packages/vlayouts/__tests__/venn/venn.test.ts | 4 ++++ packages/vlayouts/src/venn/utils/label.ts | 3 +-- 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json create mode 100644 common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json diff --git a/common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json b/common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json new file mode 100644 index 00000000..d3918007 --- /dev/null +++ b/common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "docs: update changlog of rush\n\n", + "type": "none", + "packageName": "@visactor/vlayouts" + } + ], + "packageName": "@visactor/vlayouts", + "email": "shuzhuxvchuang@163.com" +} \ No newline at end of file diff --git a/common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json b/common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json new file mode 100644 index 00000000..38a9f96f --- /dev/null +++ b/common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "docs: update changlog of rush\n\n", + "type": "none", + "packageName": "@visactor/vutils" + } + ], + "packageName": "@visactor/vutils", + "email": "shuzhuxvchuang@163.com" +} \ No newline at end of file diff --git a/packages/vlayouts/__tests__/venn/venn.test.ts b/packages/vlayouts/__tests__/venn/venn.test.ts index 6e58c092..f11ddc4b 100644 --- a/packages/vlayouts/__tests__/venn/venn.test.ts +++ b/packages/vlayouts/__tests__/venn/venn.test.ts @@ -87,4 +87,8 @@ test('Data transform of 3 element venn with empty set', async () => { expect((result[0] as IVennCircleDatum).radius).toBeCloseTo(250, 0); expect((result[0] as IVennCircleDatum).x).toBeCloseTo(250, 0); expect((result[0] as IVennCircleDatum).y).toBeCloseTo(250, 0); + + expect((result[1] as IVennCircleDatum).radius).toBeCloseTo((result[1] as IVennCircleDatum).radius, 0); + expect((result[1] as IVennCircleDatum).x).toBeCloseTo((result[1] as IVennCircleDatum).x, 0); + expect((result[1] as IVennCircleDatum).y).toBeCloseTo((result[1] as IVennCircleDatum).y, 0); }); diff --git a/packages/vlayouts/src/venn/utils/label.ts b/packages/vlayouts/src/venn/utils/label.ts index c6449c57..f480526c 100644 --- a/packages/vlayouts/src/venn/utils/label.ts +++ b/packages/vlayouts/src/venn/utils/label.ts @@ -1,4 +1,4 @@ -/* Adapted from venn.js by Ben Frederickson +``; /* Adapted from venn.js by Ben Frederickson * https://github.com/benfred/venn.js * Licensed under the MIT @@ -20,7 +20,6 @@ export function computeTextCenters( for (let i = 0; i < areas.length; ++i) { const area = areas[i].sets; if (!area || area.length === 0) { - ret[''] = { x: 0, y: 0 }; continue; } const areaIds: Record = {}; From 9a402339a058d922f608eae5bb13ea5353941222 Mon Sep 17 00:00:00 2001 From: szxc Date: Fri, 16 May 2025 14:12:40 +0800 Subject: [PATCH 03/10] docs: update changlog of rush --- .../vlayouts/feat-venn_2025-05-16-06-12.json | 11 +++++++++++ .../vutils/feat-venn_2025-05-16-06-12.json | 11 +++++++++++ packages/vlayouts/__tests__/venn/venn.test.ts | 17 +++++++++++++---- packages/vlayouts/src/venn/interface.ts | 1 + packages/vlayouts/src/venn/venn.ts | 7 ++++--- 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json create mode 100644 common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json diff --git a/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json b/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json new file mode 100644 index 00000000..35fc0cd0 --- /dev/null +++ b/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: add emptySetKey option to IVennTransformOptions", + "type": "none", + "packageName": "@visactor/vlayouts" + } + ], + "packageName": "@visactor/vlayouts", + "email": "shuzhuxvchuang@163.com" +} \ No newline at end of file diff --git a/common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json b/common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json new file mode 100644 index 00000000..bf4175bb --- /dev/null +++ b/common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: add emptySetKey option to IVennTransformOptions", + "type": "none", + "packageName": "@visactor/vutils" + } + ], + "packageName": "@visactor/vutils", + "email": "shuzhuxvchuang@163.com" +} \ No newline at end of file diff --git a/packages/vlayouts/__tests__/venn/venn.test.ts b/packages/vlayouts/__tests__/venn/venn.test.ts index f11ddc4b..cf7cb358 100644 --- a/packages/vlayouts/__tests__/venn/venn.test.ts +++ b/packages/vlayouts/__tests__/venn/venn.test.ts @@ -68,7 +68,7 @@ const data2 = [ { sets: ['A', 'B'], size: 4, label: 'A,B', stroke: 'red' } ]; -test('Data transform of 3 element venn with empty set', async () => { +test('Data transform of 2 element venn with empty set', async () => { const result = await transform( { x0: 0, @@ -88,7 +88,16 @@ test('Data transform of 3 element venn with empty set', async () => { expect((result[0] as IVennCircleDatum).x).toBeCloseTo(250, 0); expect((result[0] as IVennCircleDatum).y).toBeCloseTo(250, 0); - expect((result[1] as IVennCircleDatum).radius).toBeCloseTo((result[1] as IVennCircleDatum).radius, 0); - expect((result[1] as IVennCircleDatum).x).toBeCloseTo((result[1] as IVennCircleDatum).x, 0); - expect((result[1] as IVennCircleDatum).y).toBeCloseTo((result[1] as IVennCircleDatum).y, 0); + const circles = getCirclesFromArcs(getArcsFromPath((result[3] as IVennOverlapDatum).path)); + const expectresult = result.slice(1, 2).reverse() as IVennCircleDatum[]; + + expect(circles.length).toEqual(2); + + expect(circles[0].radius).toBeCloseTo(expectresult[0].radius, 0); + expect(circles[0].x).toBeCloseTo(expectresult[0].x, 0); + expect(circles[0].y).toBeCloseTo(expectresult[0].y, 0); + + expect(circles[1].radius).toBeCloseTo(expectresult[1].radius, 0); + expect(circles[1].x).toBeCloseTo(expectresult[1].x, 0); + expect(circles[1].y).toBeCloseTo(expectresult[1].y, 0); }); diff --git a/packages/vlayouts/src/venn/interface.ts b/packages/vlayouts/src/venn/interface.ts index 9211b757..a9cc60bf 100644 --- a/packages/vlayouts/src/venn/interface.ts +++ b/packages/vlayouts/src/venn/interface.ts @@ -9,6 +9,7 @@ export interface IVennTransformOptions extends IVennParams { valueField?: string; orientation?: number; orientationOrder?: any; + emptySetKey?: string; } export interface IVennTransformMarkOptions { diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index d2083d4e..2ac00b6f 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -1,5 +1,5 @@ import type { IPointLike } from '@visactor/vutils'; -import { array } from '@visactor/vutils'; +import { array, isEmpty } from '@visactor/vutils'; import type { IVennCircleDatum, IVennCommonDatum, @@ -23,7 +23,8 @@ export const transform = ( setField = 'sets', valueField = 'size', orientation = Math.PI / 2, - orientationOrder = null + orientationOrder = null, + emptySetKey = '' } = options; let circles: Record = {}; @@ -50,7 +51,7 @@ export const transform = ( ...area, datum: area, sets, - key: '', + key: emptySetKey, size: area[valueField], labelX: undefined, labelY: undefined, From 926aef827b09e116eb757285eee3cd1825ceab23 Mon Sep 17 00:00:00 2001 From: szxc Date: Fri, 16 May 2025 15:09:40 +0800 Subject: [PATCH 04/10] chore: delete outdated changelog files --- .../vlayouts/feat-venn_2025-05-14-13-16.json | 11 ----------- .../vlayouts/feat-venn_2025-05-15-03-24.json | 11 ----------- .../@visactor/vutils/feat-venn_2025-05-14-13-16.json | 11 ----------- .../@visactor/vutils/feat-venn_2025-05-15-03-24.json | 11 ----------- 4 files changed, 44 deletions(-) delete mode 100644 common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json delete mode 100644 common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json delete mode 100644 common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json delete mode 100644 common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json diff --git a/common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json b/common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json deleted file mode 100644 index fb5446b3..00000000 --- a/common/changes/@visactor/vlayouts/feat-venn_2025-05-14-13-16.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "Merge pull request #237 from VisActor/release/1.0.5\n\n[Auto release] release 1.0.5\n", - "type": "none", - "packageName": "@visactor/vlayouts" - } - ], - "packageName": "@visactor/vlayouts", - "email": "shuzhuxvchuang@163.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json b/common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json deleted file mode 100644 index d3918007..00000000 --- a/common/changes/@visactor/vlayouts/feat-venn_2025-05-15-03-24.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "docs: update changlog of rush\n\n", - "type": "none", - "packageName": "@visactor/vlayouts" - } - ], - "packageName": "@visactor/vlayouts", - "email": "shuzhuxvchuang@163.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json b/common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json deleted file mode 100644 index 3e1d7ad2..00000000 --- a/common/changes/@visactor/vutils/feat-venn_2025-05-14-13-16.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "Merge pull request #237 from VisActor/release/1.0.5\n\n[Auto release] release 1.0.5\n", - "type": "none", - "packageName": "@visactor/vutils" - } - ], - "packageName": "@visactor/vutils", - "email": "shuzhuxvchuang@163.com" -} \ No newline at end of file diff --git a/common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json b/common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json deleted file mode 100644 index 38a9f96f..00000000 --- a/common/changes/@visactor/vutils/feat-venn_2025-05-15-03-24.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "docs: update changlog of rush\n\n", - "type": "none", - "packageName": "@visactor/vutils" - } - ], - "packageName": "@visactor/vutils", - "email": "shuzhuxvchuang@163.com" -} \ No newline at end of file From ae68b98263348b2fe61252544e2cfb4872b18709 Mon Sep 17 00:00:00 2001 From: szxc Date: Thu, 22 May 2025 18:37:48 +0800 Subject: [PATCH 05/10] fix: filter out empty sets from upstream data in venn transformation --- packages/vlayouts/src/venn/venn.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index 2ac00b6f..843bc49b 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -30,8 +30,13 @@ export const transform = ( let circles: Record = {}; let textCenters: Record = {}; - if (upstreamData.length > 0) { - const vennData = upstreamData.map( + const nonEmptyUpstreamData = upstreamData.filter(area => { + const sets = array(area[setField]); + return !isEmpty(sets); + }); + + if (nonEmptyUpstreamData.length > 0) { + const vennData = nonEmptyUpstreamData.map( area => ({ sets: array(area[setField]), From 02fd280dd5dfe168ca494beb07569a3445d2712c Mon Sep 17 00:00:00 2001 From: szxc Date: Tue, 27 May 2025 20:55:11 +0800 Subject: [PATCH 06/10] feat: enhance venn diagram scaling to handle empty sets --- .../src/venn/utils/solution/scale-solution.ts | 33 ++++++++++++++++--- packages/vlayouts/src/venn/venn.ts | 21 ++++++++---- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/vlayouts/src/venn/utils/solution/scale-solution.ts b/packages/vlayouts/src/venn/utils/solution/scale-solution.ts index c7450b26..a4ae3a71 100644 --- a/packages/vlayouts/src/venn/utils/solution/scale-solution.ts +++ b/packages/vlayouts/src/venn/utils/solution/scale-solution.ts @@ -18,7 +18,8 @@ export function scaleSolution( width: number, height: number, x0: number, - y0: number + y0: number, + hasEmptySet: boolean = false ): Record { width = Math.max(width, 1); height = Math.max(height, 1); @@ -44,12 +45,34 @@ export function scaleSolution( const xScaling = width / (xRange.max - xRange.min); const yScaling = height / (yRange.max - yRange.min); - const scaling = Math.min(yScaling, xScaling); - // while we're at it, center the diagram too - const xOffset = (width - (xRange.max - xRange.min) * scaling) / 2; - const yOffset = (height - (yRange.max - yRange.min) * scaling) / 2; + let scaling: number; + let xOffset: number; + let yOffset: number; + if (hasEmptySet) { + const containerRadius = Math.min(width, height) / 2; + + const centerX = (xRange.min + xRange.max) / 2; + const centerY = (yRange.min + yRange.max) / 2; + + let diagramRadius = 0; + for (const circle of circles) { + const distanceToCenter = Math.sqrt(Math.pow(circle.x - centerX, 2) + Math.pow(circle.y - centerY, 2)); + const maxDistanceForThisCircle = distanceToCenter + circle.radius; + diagramRadius = Math.max(diagramRadius, maxDistanceForThisCircle); + } + scaling = containerRadius / diagramRadius; + + xOffset = (width - (xRange.max - xRange.min) * scaling) / 2; + yOffset = (height - (yRange.max - yRange.min) * scaling) / 2; + } else { + scaling = Math.min(yScaling, xScaling); + + // while we're at it, center the diagram too + xOffset = (width - (xRange.max - xRange.min) * scaling) / 2; + yOffset = (height - (yRange.max - yRange.min) * scaling) / 2; + } const scaled: Record = {}; for (let i = 0; i < circles.length; ++i) { const circle = circles[i]; diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index 843bc49b..01bdbbf4 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -1,5 +1,5 @@ import type { IPointLike } from '@visactor/vutils'; -import { array, isEmpty } from '@visactor/vutils'; +import { array, has, isEmpty } from '@visactor/vutils'; import type { IVennCircleDatum, IVennCommonDatum, @@ -24,17 +24,24 @@ export const transform = ( valueField = 'size', orientation = Math.PI / 2, orientationOrder = null, - emptySetKey = '' + emptySetKey = 'rest' } = options; - let circles: Record = {}; let textCenters: Record = {}; - const nonEmptyUpstreamData = upstreamData.filter(area => { + let hasEmptySet = false; + hasEmptySet = upstreamData.some(area => { const sets = array(area[setField]); - return !isEmpty(sets); + return !sets || sets.length === 0; }); + const nonEmptyUpstreamData = hasEmptySet + ? upstreamData.filter(area => { + const sets = array(area[setField]); + return !isEmpty(sets); + }) + : upstreamData; + if (nonEmptyUpstreamData.length > 0) { const vennData = nonEmptyUpstreamData.map( area => @@ -45,7 +52,7 @@ export const transform = ( ); let solution = venn(vennData, options); solution = normalizeSolution(solution, orientation, orientationOrder); - circles = scaleSolution(solution, x1 - x0, y1 - y0, x0, y0); + circles = scaleSolution(solution, x1 - x0, y1 - y0, x0, y0, hasEmptySet); textCenters = computeTextCenters(circles, vennData); } @@ -63,7 +70,7 @@ export const transform = ( type: 'circle', x: x0 + (x1 - x0) / 2, y: y0 + (y1 - y0) / 2, - radius: Math.max(x1 - x0, y1 - y0) / 2 + radius: Math.min(x1 - x0, y1 - y0) / 2 } as IVennCircleDatum; } const key = sets.toString(); From fd39d9cc8d176d323e02360c8bcf4f962902e517 Mon Sep 17 00:00:00 2001 From: szxc Date: Tue, 27 May 2025 21:15:17 +0800 Subject: [PATCH 07/10] refactor: simplify empty set handling and centralize diagram offset --- .../src/venn/utils/solution/scale-solution.ts | 12 +++--------- packages/vlayouts/src/venn/venn.ts | 10 +++------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/packages/vlayouts/src/venn/utils/solution/scale-solution.ts b/packages/vlayouts/src/venn/utils/solution/scale-solution.ts index a4ae3a71..379bec9e 100644 --- a/packages/vlayouts/src/venn/utils/solution/scale-solution.ts +++ b/packages/vlayouts/src/venn/utils/solution/scale-solution.ts @@ -47,8 +47,6 @@ export function scaleSolution( const yScaling = height / (yRange.max - yRange.min); let scaling: number; - let xOffset: number; - let yOffset: number; if (hasEmptySet) { const containerRadius = Math.min(width, height) / 2; @@ -63,16 +61,12 @@ export function scaleSolution( diagramRadius = Math.max(diagramRadius, maxDistanceForThisCircle); } scaling = containerRadius / diagramRadius; - - xOffset = (width - (xRange.max - xRange.min) * scaling) / 2; - yOffset = (height - (yRange.max - yRange.min) * scaling) / 2; } else { scaling = Math.min(yScaling, xScaling); - - // while we're at it, center the diagram too - xOffset = (width - (xRange.max - xRange.min) * scaling) / 2; - yOffset = (height - (yRange.max - yRange.min) * scaling) / 2; } + // while we're at it, center the diagram too + const xOffset = (width - (xRange.max - xRange.min) * scaling) / 2; + const yOffset = (height - (yRange.max - yRange.min) * scaling) / 2; const scaled: Record = {}; for (let i = 0; i < circles.length; ++i) { const circle = circles[i]; diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index 01bdbbf4..543f4cc9 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -29,17 +29,13 @@ export const transform = ( let circles: Record = {}; let textCenters: Record = {}; - let hasEmptySet = false; - hasEmptySet = upstreamData.some(area => { + const hasEmptySet = upstreamData.some(area => { const sets = array(area[setField]); return !sets || sets.length === 0; }); const nonEmptyUpstreamData = hasEmptySet - ? upstreamData.filter(area => { - const sets = array(area[setField]); - return !isEmpty(sets); - }) + ? upstreamData.filter(area => !isEmpty(array(area[setField]))) : upstreamData; if (nonEmptyUpstreamData.length > 0) { @@ -58,7 +54,7 @@ export const transform = ( const data = upstreamData.map(area => { const sets = array(area[setField]); - if (!sets || sets.length === 0) { + if (hasEmptySet) { return { ...area, datum: area, From 97c3e87120aabe744d0feee43ffbca6d3fa239df Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 1 Jun 2025 22:09:06 +0800 Subject: [PATCH 08/10] refactor: simplify empty set handling and centralize diagram offsets --- packages/vlayouts/src/venn/interface.ts | 1 - packages/vlayouts/src/venn/utils/label.ts | 5 +---- packages/vlayouts/src/venn/venn.ts | 17 +++++++---------- packages/vutils/src/geo/circle-intersection.ts | 11 ----------- 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/packages/vlayouts/src/venn/interface.ts b/packages/vlayouts/src/venn/interface.ts index a9cc60bf..9211b757 100644 --- a/packages/vlayouts/src/venn/interface.ts +++ b/packages/vlayouts/src/venn/interface.ts @@ -9,7 +9,6 @@ export interface IVennTransformOptions extends IVennParams { valueField?: string; orientation?: number; orientationOrder?: any; - emptySetKey?: string; } export interface IVennTransformMarkOptions { diff --git a/packages/vlayouts/src/venn/utils/label.ts b/packages/vlayouts/src/venn/utils/label.ts index f480526c..8d7f6fa9 100644 --- a/packages/vlayouts/src/venn/utils/label.ts +++ b/packages/vlayouts/src/venn/utils/label.ts @@ -1,4 +1,4 @@ -``; /* Adapted from venn.js by Ben Frederickson +/* Adapted from venn.js by Ben Frederickson * https://github.com/benfred/venn.js * Licensed under the MIT @@ -19,9 +19,6 @@ export function computeTextCenters( const overlapped = getOverlappingCircles(circles); for (let i = 0; i < areas.length; ++i) { const area = areas[i].sets; - if (!area || area.length === 0) { - continue; - } const areaIds: Record = {}; const exclude: Record = {}; for (let j = 0; j < area.length; ++j) { diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index 543f4cc9..bbe23a4a 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -1,5 +1,5 @@ import type { IPointLike } from '@visactor/vutils'; -import { array, has, isEmpty } from '@visactor/vutils'; +import { array, isEmpty } from '@visactor/vutils'; import type { IVennCircleDatum, IVennCommonDatum, @@ -23,8 +23,7 @@ export const transform = ( setField = 'sets', valueField = 'size', orientation = Math.PI / 2, - orientationOrder = null, - emptySetKey = 'rest' + orientationOrder = null } = options; let circles: Record = {}; let textCenters: Record = {}; @@ -34,12 +33,10 @@ export const transform = ( return !sets || sets.length === 0; }); - const nonEmptyUpstreamData = hasEmptySet - ? upstreamData.filter(area => !isEmpty(array(area[setField]))) - : upstreamData; + const nonEmptyData = hasEmptySet ? upstreamData.filter(area => !isEmpty(array(area[setField]))) : upstreamData; - if (nonEmptyUpstreamData.length > 0) { - const vennData = nonEmptyUpstreamData.map( + if (nonEmptyData.length > 0) { + const vennData = nonEmptyData.map( area => ({ sets: array(area[setField]), @@ -54,12 +51,12 @@ export const transform = ( const data = upstreamData.map(area => { const sets = array(area[setField]); - if (hasEmptySet) { + if (!sets || sets.length === 0) { return { ...area, datum: area, sets, - key: emptySetKey, + key: 'others', size: area[valueField], labelX: undefined, labelY: undefined, diff --git a/packages/vutils/src/geo/circle-intersection.ts b/packages/vutils/src/geo/circle-intersection.ts index 47a15dee..bf9a319c 100644 --- a/packages/vutils/src/geo/circle-intersection.ts +++ b/packages/vutils/src/geo/circle-intersection.ts @@ -16,17 +16,6 @@ import type { ICircleArc, ICircle, IIntersectPoint, IOverlapAreaStats } from './ /** Returns the intersection area of a bunch of circles (where each circle is an object having an x,y and radius property) */ export function intersectionArea(circles: ICircle[], stats?: IOverlapAreaStats) { - if (!circles || !circles.length) { - if (stats) { - stats.area = 0; - stats.arcArea = 0; - stats.polygonArea = 0; - stats.arcs = []; - stats.innerPoints = []; - stats.intersectionPoints = []; - } - return 0; - } // get all the intersection points of the circles const intersectionPoints = getIntersectionPoints(circles); From 990870a2ecaeefb95ef4fd0d7ad8024109632352 Mon Sep 17 00:00:00 2001 From: szxc Date: Sun, 15 Jun 2025 22:17:01 +0800 Subject: [PATCH 09/10] test: improve tests for 2 and 1 element venn diagrams with empty sets --- packages/vlayouts/__tests__/venn/venn.test.ts | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/vlayouts/__tests__/venn/venn.test.ts b/packages/vlayouts/__tests__/venn/venn.test.ts index cf7cb358..1528a782 100644 --- a/packages/vlayouts/__tests__/venn/venn.test.ts +++ b/packages/vlayouts/__tests__/venn/venn.test.ts @@ -89,15 +89,41 @@ test('Data transform of 2 element venn with empty set', async () => { expect((result[0] as IVennCircleDatum).y).toBeCloseTo(250, 0); const circles = getCirclesFromArcs(getArcsFromPath((result[3] as IVennOverlapDatum).path)); - const expectresult = result.slice(1, 2).reverse() as IVennCircleDatum[]; + const circleA = result[2] as IVennCircleDatum; + const circleB = result[1] as IVennCircleDatum; expect(circles.length).toEqual(2); - expect(circles[0].radius).toBeCloseTo(expectresult[0].radius, 0); - expect(circles[0].x).toBeCloseTo(expectresult[0].x, 0); - expect(circles[0].y).toBeCloseTo(expectresult[0].y, 0); + expect(circles[0].radius).toBeCloseTo(circleA.radius, 0); + expect(circles[0].x).toBeCloseTo(circleA.x, 0); + expect(circles[0].y).toBeCloseTo(circleA.y, 0); - expect(circles[1].radius).toBeCloseTo(expectresult[1].radius, 0); - expect(circles[1].x).toBeCloseTo(expectresult[1].x, 0); - expect(circles[1].y).toBeCloseTo(expectresult[1].y, 0); + expect(circles[1].radius).toBeCloseTo(circleB.radius, 0); + expect(circles[1].x).toBeCloseTo(circleB.x, 0); + expect(circles[1].y).toBeCloseTo(circleB.y, 0); +}); + +test('Data transform of 1 element venn with empty set and no overlaps', async () => { + const result = await transform( + { + x0: 0, + y0: 0, + x1: 500, + y1: 500 + }, + data2.slice(0, 2) + ); + + expect(result.length).toEqual(2); + + expect(result[0].type).toEqual('circle'); + expect(result[1].type).toEqual('circle'); + + expect((result[0] as IVennCircleDatum).radius).toBeCloseTo(250, 0); + expect((result[0] as IVennCircleDatum).x).toBeCloseTo(250, 0); + expect((result[0] as IVennCircleDatum).y).toBeCloseTo(250, 0); + + expect((result[1] as IVennCircleDatum).radius).toBeCloseTo((result[0] as IVennCircleDatum).radius, 0); + expect((result[1] as IVennCircleDatum).x).toBeCloseTo((result[0] as IVennCircleDatum).x, 0); + expect((result[1] as IVennCircleDatum).y).toBeCloseTo((result[0] as IVennCircleDatum).y, 0); }); From ba7a14be8e806db94a4fd7a75b1780a452503c15 Mon Sep 17 00:00:00 2001 From: szxc Date: Thu, 26 Jun 2025 15:42:30 +0800 Subject: [PATCH 10/10] feat: add emptySet option to IVennTransformOptions and update transform logic --- .../vlayouts/feat-venn_2025-05-16-06-12.json | 2 +- .../@visactor/vutils/feat-venn_2025-05-16-06-12.json | 11 ----------- packages/vlayouts/src/venn/interface.ts | 1 + packages/vlayouts/src/venn/venn.ts | 5 +++-- 4 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json diff --git a/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json b/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json index 35fc0cd0..c0b2bca4 100644 --- a/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json +++ b/common/changes/@visactor/vlayouts/feat-venn_2025-05-16-06-12.json @@ -1,7 +1,7 @@ { "changes": [ { - "comment": "fix: add emptySetKey option to IVennTransformOptions", + "comment": "feat: add emptySet option to VennTransform", "type": "none", "packageName": "@visactor/vlayouts" } diff --git a/common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json b/common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json deleted file mode 100644 index bf4175bb..00000000 --- a/common/changes/@visactor/vutils/feat-venn_2025-05-16-06-12.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "fix: add emptySetKey option to IVennTransformOptions", - "type": "none", - "packageName": "@visactor/vutils" - } - ], - "packageName": "@visactor/vutils", - "email": "shuzhuxvchuang@163.com" -} \ No newline at end of file diff --git a/packages/vlayouts/src/venn/interface.ts b/packages/vlayouts/src/venn/interface.ts index 9211b757..a9cc60bf 100644 --- a/packages/vlayouts/src/venn/interface.ts +++ b/packages/vlayouts/src/venn/interface.ts @@ -9,6 +9,7 @@ export interface IVennTransformOptions extends IVennParams { valueField?: string; orientation?: number; orientationOrder?: any; + emptySetKey?: string; } export interface IVennTransformMarkOptions { diff --git a/packages/vlayouts/src/venn/venn.ts b/packages/vlayouts/src/venn/venn.ts index bbe23a4a..a11f3c22 100644 --- a/packages/vlayouts/src/venn/venn.ts +++ b/packages/vlayouts/src/venn/venn.ts @@ -23,7 +23,8 @@ export const transform = ( setField = 'sets', valueField = 'size', orientation = Math.PI / 2, - orientationOrder = null + orientationOrder = null, + emptySetKey } = options; let circles: Record = {}; let textCenters: Record = {}; @@ -56,7 +57,7 @@ export const transform = ( ...area, datum: area, sets, - key: 'others', + key: emptySetKey || 'others', size: area[valueField], labelX: undefined, labelY: undefined,