From 3c28278d7a87f7d18236bb5f6a63fab169dc2fc4 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 19:41:21 +0800 Subject: [PATCH 1/7] fix(wordCloud): support wordCloud Chart with negative data and one data. fix VChart#299 & VChart#373 --- .../__tests__/wordcloud.test.ts | 127 ++++++++++++++++++ packages/vgrammar-wordcloud/src/wordcloud.ts | 29 ++-- 2 files changed, 145 insertions(+), 11 deletions(-) diff --git a/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts b/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts index eb70e41c7..a08a1a103 100644 --- a/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts +++ b/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts @@ -77,3 +77,130 @@ test('Wordcloud generates wordcloud layout', async () => { expect(result[0].x).toBe(250); expect(result[0].y).toBe(250); }); + +test('Wordcloud generates wordcloud layout with negative data', async () => { + const data = [ + { text: 'foo', size: -49, index: 0 }, + { text: 'bar', size: 36, index: 1 }, + { text: 'baz', size: 25, index: 2 }, + { text: 'abc', size: 1, index: 3 } + ]; + + const result = await transform( + { + size: [500, 500], + text: { field: 'text' }, + fontSize: { field: 'size' }, + fontSizeRange: [1, 7] + }, + data + ); + expect(result.length).toBe(data.length); + expect(result[0].fontFamily).toBe('sans-serif'); + expect(result[0].fontSize).toBe(7); + expect(result[0].fontStyle).toBe('normal'); + expect(result[0].fontWeight).toBe('normal'); + expect(result[0].x).toBe(250); + expect(result[0].y).toBe(250); +}); + +test('Wordcloud generates wordcloud layout with negative data and -domain[0] === domain[1]', async () => { + const data = [ + { text: 'foo', size: -49, index: 0 }, + { text: 'bar', size: 49, index: 1 } + ]; + + const result = await transform( + { + size: [500, 500], + text: { field: 'text' }, + fontSize: { field: 'size' }, + fontSizeRange: [1, 7] + }, + data + ); + expect(result.length).toBe(data.length); + expect(result[0].fontFamily).toBe('sans-serif'); + expect(result[0].fontSize).toBe(7); + expect(result[0].fontStyle).toBe('normal'); + expect(result[0].fontWeight).toBe('normal'); + expect(result[0].x).toBe(250); + expect(result[0].y).toBe(250); + + expect(result.length).toBe(data.length); + expect(result[1].fontFamily).toBe('sans-serif'); + expect(result[1].fontSize).toBe(1); + expect(result[1].fontStyle).toBe('normal'); + expect(result[1].fontWeight).toBe('normal'); + expect(result[1].x).toBe(253); + expect(result[1].y).toBe(253); +}); + +test('Wordcloud generates wordcloud layout with one data', async () => { + const data = [{ text: 'foo', size: 49, index: 0 }]; + + const result = await transform( + { + size: [500, 500], + text: { field: 'text' }, + fontSize: { field: 'size' }, + fontSizeRange: [1, 7] + }, + data + ); + expect(result.length).toBe(data.length); + expect(result[0].fontFamily).toBe('sans-serif'); + expect(result[0].fontSize).toBe(7); + expect(result[0].fontStyle).toBe('normal'); + expect(result[0].fontWeight).toBe('normal'); + expect(result[0].x).toBe(250); + expect(result[0].y).toBe(250); +}); + +test('Wordcloud generates wordcloud layout with domain[0] == domain[1] & domain[0] < 0', async () => { + const data = [ + { text: 'foo', size: -49, index: 0 }, + { text: 'bar', size: -49, index: 1 } + ]; + + const result = await transform( + { + size: [500, 500], + text: { field: 'text' }, + fontSize: { field: 'size' }, + fontSizeRange: [1, 7] + }, + data + ); + expect(result.length).toBe(data.length); + expect(result[0].fontFamily).toBe('sans-serif'); + expect(result[0].fontSize).toBe(1); + expect(result[0].fontStyle).toBe('normal'); + expect(result[0].fontWeight).toBe('normal'); + expect(result[0].x).toBe(250); + expect(result[0].y).toBe(250); +}); + +test('Wordcloud generates wordcloud layout with domain[0] == domain[1] & domain[0] > 0', async () => { + const data = [ + { text: 'foo', size: 49, index: 0 }, + { text: 'bar', size: 49, index: 1 } + ]; + + const result = await transform( + { + size: [500, 500], + text: { field: 'text' }, + fontSize: { field: 'size' }, + fontSizeRange: [1, 7] + }, + data + ); + expect(result.length).toBe(data.length); + expect(result[0].fontFamily).toBe('sans-serif'); + expect(result[0].fontSize).toBe(1); + expect(result[0].fontStyle).toBe('normal'); + expect(result[0].fontWeight).toBe('normal'); + expect(result[0].x).toBe(250); + expect(result[0].y).toBe(250); +}); diff --git a/packages/vgrammar-wordcloud/src/wordcloud.ts b/packages/vgrammar-wordcloud/src/wordcloud.ts index 0fac385ad..9f320e979 100644 --- a/packages/vgrammar-wordcloud/src/wordcloud.ts +++ b/packages/vgrammar-wordcloud/src/wordcloud.ts @@ -90,9 +90,7 @@ export const transform = ( // 只有fontSize不为固定值时,fontSizeRange才生效 if (fontSizeRange && !isNumber(fontSize)) { const fsize: any = fontSize; - fontSize = (datum: any) => { - return sqrtScale(fsize(datum), extent(fsize, data), fontSizeRange as number[]); - }; + fontSize = datum => generateSqrtScale(extent(fsize, data), fontSizeRange as number[])(fsize(datum)); } let Layout: any = CloudLayout; @@ -169,13 +167,22 @@ const field = (option: FieldOption | TagItemAttribute) => { return (datum: any) => datum[(option as FieldOption).field] as T; }; -// 模拟sqrt scale -const sqrtScale = (datum: any, domain: number[], range: number[]) => { - return ( - ((Math.sqrt(datum) - Math.sqrt(domain[0])) / (Math.sqrt(domain[1]) - Math.sqrt(domain[0]))) * - (range[1] - range[0]) + - range[0] - ); +const sqrt = (x: number) => { + return x < 0 ? -Math.sqrt(-x) : Math.sqrt(x); +}; + +// simulation sqrt scale +const generateSqrtScale = (domain: number[], range: number[]) => { + if (domain[0] === domain[1]) { + return (datum: number) => range[0]; // match smallest fontsize + } + + const s0 = sqrt(domain[0]); + const s1 = sqrt(domain[1]); + const min = Math.min(s0, s1); + const max = Math.max(s0, s1); + + return (datum: number) => ((sqrt(datum) - min) / (max - min)) * (range[1] - range[0]) + range[0]; }; const extent = (field: any, data: any[]) => { @@ -197,7 +204,7 @@ const extent = (field: any, data: any[]) => { // 如果单条数据,匹配最大字号 if (data.length === 1 && min === max) { - min -= 10000; + min = max - 1; } return [min, max]; From 2b3b2c681ecdf305b3956df7ae0ed43b29104249 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 19:51:19 +0800 Subject: [PATCH 2/7] fix(wordCloud): revert to original logic --- packages/vgrammar-wordcloud/src/wordcloud.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vgrammar-wordcloud/src/wordcloud.ts b/packages/vgrammar-wordcloud/src/wordcloud.ts index 9f320e979..5588725d3 100644 --- a/packages/vgrammar-wordcloud/src/wordcloud.ts +++ b/packages/vgrammar-wordcloud/src/wordcloud.ts @@ -204,7 +204,7 @@ const extent = (field: any, data: any[]) => { // 如果单条数据,匹配最大字号 if (data.length === 1 && min === max) { - min = max - 1; + min -= 1000; } return [min, max]; From 19bc5b6a0b155d9fb704ce442b68dc3f4dbe534c Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 19:52:11 +0800 Subject: [PATCH 3/7] chore: update VGrammar rush change log --- .../vgrammar/fix-word-cloud-dev_2023-08-03-11-51.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@visactor/vgrammar/fix-word-cloud-dev_2023-08-03-11-51.json diff --git a/common/changes/@visactor/vgrammar/fix-word-cloud-dev_2023-08-03-11-51.json b/common/changes/@visactor/vgrammar/fix-word-cloud-dev_2023-08-03-11-51.json new file mode 100644 index 000000000..a8fd60bc1 --- /dev/null +++ b/common/changes/@visactor/vgrammar/fix-word-cloud-dev_2023-08-03-11-51.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@visactor/vgrammar", + "comment": "fix(wordCloud): support wordCloud Chart with negative data and one data. fix VisActor/VChart#299, fix VisActor/VChart#373", + "type": "none" + } + ], + "packageName": "@visactor/vgrammar" +} \ No newline at end of file From 9e034741e7bb01a542e223a02b12eb79b2414b34 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 19:53:59 +0800 Subject: [PATCH 4/7] fix(wordCloud): revert to original logic --- packages/vgrammar-wordcloud/src/wordcloud.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vgrammar-wordcloud/src/wordcloud.ts b/packages/vgrammar-wordcloud/src/wordcloud.ts index 5588725d3..4716bb5cc 100644 --- a/packages/vgrammar-wordcloud/src/wordcloud.ts +++ b/packages/vgrammar-wordcloud/src/wordcloud.ts @@ -204,7 +204,7 @@ const extent = (field: any, data: any[]) => { // 如果单条数据,匹配最大字号 if (data.length === 1 && min === max) { - min -= 1000; + min -= 10000; } return [min, max]; From 85f0fa9933c313833651057013a446578fc35b2f Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 20:01:15 +0800 Subject: [PATCH 5/7] chore: unit test error --- packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts b/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts index a08a1a103..c60668f68 100644 --- a/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts +++ b/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts @@ -132,8 +132,8 @@ test('Wordcloud generates wordcloud layout with negative data and -domain[0] === expect(result[1].fontSize).toBe(1); expect(result[1].fontStyle).toBe('normal'); expect(result[1].fontWeight).toBe('normal'); - expect(result[1].x).toBe(253); - expect(result[1].y).toBe(253); + expect(result[1].x).toBe(252); + expect(result[1].y).toBe(252); }); test('Wordcloud generates wordcloud layout with one data', async () => { From b2a23b77a490c44870991b11f7e28b897a7d56f2 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 20:05:45 +0800 Subject: [PATCH 6/7] chore: unit test error --- packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts b/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts index c60668f68..49eda302a 100644 --- a/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts +++ b/packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts @@ -126,14 +126,6 @@ test('Wordcloud generates wordcloud layout with negative data and -domain[0] === expect(result[0].fontWeight).toBe('normal'); expect(result[0].x).toBe(250); expect(result[0].y).toBe(250); - - expect(result.length).toBe(data.length); - expect(result[1].fontFamily).toBe('sans-serif'); - expect(result[1].fontSize).toBe(1); - expect(result[1].fontStyle).toBe('normal'); - expect(result[1].fontWeight).toBe('normal'); - expect(result[1].x).toBe(252); - expect(result[1].y).toBe(252); }); test('Wordcloud generates wordcloud layout with one data', async () => { From 64852b1a93bb1eacd0331d7f5a53b0697e7c8cb7 Mon Sep 17 00:00:00 2001 From: skie1997 Date: Thu, 3 Aug 2023 21:14:05 +0800 Subject: [PATCH 7/7] fix(wordCloud): save local variable to avoid repeat compute --- packages/vgrammar-wordcloud/src/wordcloud.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/vgrammar-wordcloud/src/wordcloud.ts b/packages/vgrammar-wordcloud/src/wordcloud.ts index 4716bb5cc..7395f25fa 100644 --- a/packages/vgrammar-wordcloud/src/wordcloud.ts +++ b/packages/vgrammar-wordcloud/src/wordcloud.ts @@ -90,7 +90,10 @@ export const transform = ( // 只有fontSize不为固定值时,fontSizeRange才生效 if (fontSizeRange && !isNumber(fontSize)) { const fsize: any = fontSize; - fontSize = datum => generateSqrtScale(extent(fsize, data), fontSizeRange as number[])(fsize(datum)); + fontSize = datum => { + const fontSizeSqrtScale = generateSqrtScale(extent(fsize, data), fontSizeRange as number[]); + return fontSizeSqrtScale(fsize(datum)); + }; } let Layout: any = CloudLayout;