Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(wordCloud): support wordCloud Chart with negative data and one da… #97

Merged
merged 7 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
119 changes: 119 additions & 0 deletions packages/vgrammar-wordcloud/__tests__/wordcloud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,122 @@ 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);
});

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);
});
28 changes: 19 additions & 9 deletions packages/vgrammar-wordcloud/src/wordcloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ 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 => {
const fontSizeSqrtScale = generateSqrtScale(extent(fsize, data), fontSizeRange as number[]);
return fontSizeSqrtScale(fsize(datum));
};
}

Expand Down Expand Up @@ -169,13 +170,22 @@ const field = <T>(option: FieldOption | TagItemAttribute<T>) => {
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[]) => {
Expand Down