Skip to content

Commit 471a7dd

Browse files
lxfu1liufu.lf
and
liufu.lf
authored
feat: scatter chart supported quadrant (#1522)
* feat: scatter chart supported quadrant * fix: resolve conversation Co-authored-by: liufu.lf <[email protected]>
1 parent 5d050b4 commit 471a7dd

File tree

7 files changed

+282
-16
lines changed

7 files changed

+282
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Scatter } from '../../../../src';
2+
import { data } from '../../../data/gender';
3+
import { createDiv } from '../../../utils/dom';
4+
5+
describe('scatter', () => {
6+
it('size: number options', () => {
7+
const scatter = new Scatter(createDiv(), {
8+
width: 400,
9+
height: 300,
10+
appendPadding: 10,
11+
data,
12+
xField: 'weight',
13+
yField: 'height',
14+
sizeField: 'weight',
15+
size: [5, 10],
16+
xAxis: {
17+
nice: true,
18+
},
19+
quadrant: {
20+
xBaseline: 80,
21+
yBaseline: 170,
22+
labels: [
23+
{
24+
content: 'Male decrease,\nfemale increase',
25+
},
26+
{
27+
content: 'Female decrease,\nmale increase',
28+
},
29+
{
30+
content: 'Female & male decrease',
31+
},
32+
{
33+
content: 'Female &\n male increase',
34+
},
35+
],
36+
},
37+
});
38+
39+
scatter.render();
40+
41+
// @ts-ignore
42+
const { option } = scatter.chart.annotation();
43+
expect(option.length).toBe(10);
44+
const regions = option.filter((item) => item.type === 'region');
45+
const texts = option.filter((item) => item.type === 'text');
46+
const lines = option.filter((item) => item.type === 'line');
47+
expect(regions.length).toBe(4);
48+
expect(texts.length).toBe(4);
49+
expect(lines.length).toBe(2);
50+
// @ts-ignore
51+
expect(regions[0].style.opacity).toBe(0.4);
52+
expect(regions[0].top).not.toBeTruthy();
53+
expect(texts[0].top).toBeTruthy();
54+
// @ts-ignore
55+
const hasLabel = texts.find((item) => item.content === 'Female & male decrease');
56+
expect(hasLabel).toBeTruthy();
57+
// @ts-ignore
58+
expect(lines[0].style.lineWidth).toBe(1);
59+
// @ts-ignore
60+
expect(lines[0].end).toEqual(['max', 170]);
61+
});
62+
});

examples/scatter/basic/demo/meta.json

+8
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343
"en": "Bubble chart"
4444
},
4545
"screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*_lGkQrhj53gAAAAAAAAAAABkARQnAQ"
46+
},
47+
{
48+
"filename": "quadrant.ts",
49+
"title": {
50+
"zh": "散点图-气泡四象限",
51+
"en": "Bubble chart quadrant"
52+
},
53+
"screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*tdedT4uaPaYAAAAAAAAAAABkARQnAQ"
4654
}
4755
]
4856
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Scatter } from '@antv/g2plot';
2+
3+
fetch('https://gw.alipayobjects.com/os/bmw-prod/0b37279d-1674-42b4-b285-29683747ad9a.json')
4+
.then((res) => res.json())
5+
.then((data) => {
6+
const scatterPlot = new Scatter('container', {
7+
appendPadding: 30,
8+
data,
9+
xField: 'change in female rate',
10+
yField: 'change in male rate',
11+
sizeField: 'pop',
12+
colorField: 'continent',
13+
color: ['#ffd500', '#82cab2', '#193442', '#d18768', '#7e827a'],
14+
size: [4, 30],
15+
shape: 'circle',
16+
pointStyle: {
17+
fillOpacity: 0.8,
18+
},
19+
xAxis: {
20+
min: -25,
21+
max: 5,
22+
grid: {
23+
line: {
24+
style: {
25+
stroke: '#eee',
26+
},
27+
},
28+
},
29+
},
30+
quadrant: {
31+
xBaseline: 0,
32+
yBaseline: 0,
33+
labels: [
34+
{
35+
content: 'Male decrease,\nfemale increase',
36+
},
37+
{
38+
content: 'Female decrease,\nmale increase',
39+
},
40+
{
41+
content: 'Female & male decrease',
42+
},
43+
{
44+
content: 'Female &\n male increase',
45+
},
46+
],
47+
},
48+
});
49+
scatterPlot.render();
50+
});

src/plots/scatter/adaptor.ts

+45-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { isFunction, isNumber, isString } from '@antv/util';
1+
import { isFunction, isNumber, isString, deepMix } from '@antv/util';
22
import { Params } from '../../core/adaptor';
33
import { flow } from '../../utils';
44
import { tooltip, interaction, animation, theme, scale } from '../../adaptor/common';
55
import { findGeometry } from '../../utils';
6+
import { getQuadrantDefaultConfig } from './util';
67
import { ScatterOptions } from './types';
78

89
/**
@@ -143,12 +144,54 @@ function label(params: Params<ScatterOptions>): Params<ScatterOptions> {
143144
return params;
144145
}
145146

147+
/**
148+
* 四象限
149+
* @param params
150+
*/
151+
function quadrant(params: Params<ScatterOptions>): Params<ScatterOptions> {
152+
const { chart, options } = params;
153+
const { quadrant } = options;
154+
155+
if (quadrant) {
156+
const { xBaseline = 0, yBaseline = 0, labels, regionStyle, lineStyle } = quadrant;
157+
const defaultConfig = getQuadrantDefaultConfig(xBaseline, yBaseline);
158+
// 仅支持四象限
159+
const quadrants = new Array(4).join(',').split(',');
160+
quadrants.forEach((_: string, index: number) => {
161+
chart.annotation().region({
162+
top: false,
163+
...defaultConfig.regionStyle[index].position,
164+
style: deepMix({}, defaultConfig.regionStyle[index].style, regionStyle?.[index]),
165+
});
166+
chart.annotation().text({
167+
top: true,
168+
...deepMix({}, defaultConfig.labelStyle[index], labels?.[index]),
169+
});
170+
});
171+
// 生成坐标轴
172+
chart.annotation().line({
173+
top: false,
174+
start: ['min', yBaseline],
175+
end: ['max', yBaseline],
176+
style: deepMix({}, defaultConfig.lineStyle, lineStyle),
177+
});
178+
chart.annotation().line({
179+
top: false,
180+
start: [xBaseline, 'min'],
181+
end: [xBaseline, 'max'],
182+
style: deepMix({}, defaultConfig.lineStyle, lineStyle),
183+
});
184+
}
185+
186+
return params;
187+
}
188+
146189
/**
147190
* 散点图适配器
148191
* @param chart
149192
* @param options
150193
*/
151194
export function adaptor(params: Params<ScatterOptions>) {
152195
// flow 的方式处理所有的配置到 G2 API
153-
return flow(field, meta, axis, legend, tooltip, style, label, interaction, animation, theme)(params);
196+
return flow(field, meta, axis, legend, tooltip, style, label, interaction, quadrant, animation, theme)(params);
154197
}

src/plots/scatter/types.ts

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Options } from '../../types';
1+
import { Options, RegionPositionBaseOption, TextOption, AnnotationPosition } from '../../types';
22
import { ShapeStyle } from '../../types/style';
3-
import { Label } from '../../types/label';
43

54
interface PointStyle {
65
/** 填充色 会覆盖 color 配置 */
@@ -19,26 +18,21 @@ interface PointStyle {
1918
readonly strokeOpacity?: number;
2019
}
2120

21+
interface Labels extends Omit<TextOption, 'position'> {
22+
position?: AnnotationPosition;
23+
}
24+
2225
interface Quadrant {
23-
/** 是否显示 */
24-
readonly visible?: boolean;
2526
/** x 方向上的象限分割基准线,默认为 0 */
2627
readonly xBaseline?: number;
2728
/** y 方向上的象限分割基准线,默认为 0 */
2829
readonly yBaseline?: number;
2930
/** 配置象限分割线的样式 */
30-
readonly lineStyle?: ShapeStyle;
31+
readonly lineStyle?: RegionPositionBaseOption;
3132
/** 象限样式 */
32-
readonly regionStyle?: RegionStyle[];
33+
readonly regionStyle?: RegionPositionBaseOption[];
3334
/** 象限文本配置 */
34-
readonly label?: Label;
35-
}
36-
37-
interface RegionStyle {
38-
/** 填充色 */
39-
readonly fill?: string;
40-
/** 不透明度 */
41-
readonly opacity?: number;
35+
readonly labels?: Labels[];
4236
}
4337

4438
interface TrendLine {

src/plots/scatter/util.ts

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* 获取四象限默认配置
3+
* @param {number} xBaseline
4+
* @param {number} yBaseline
5+
*/
6+
export function getQuadrantDefaultConfig(xBaseline: number, yBaseline: number) {
7+
// 文本便宜距离
8+
const textOffset = 10;
9+
// 四象限默认样式
10+
const defaultConfig: { [key: string]: any } = {
11+
regionStyle: [
12+
{
13+
position: {
14+
start: [xBaseline, 'max'],
15+
end: ['max', yBaseline],
16+
},
17+
style: {
18+
fill: '#d8d0c0',
19+
opacity: 0.4,
20+
},
21+
},
22+
{
23+
position: {
24+
start: ['min', 'max'],
25+
end: [xBaseline, yBaseline],
26+
},
27+
style: {
28+
fill: '#a3dda1',
29+
opacity: 0.4,
30+
},
31+
},
32+
{
33+
position: {
34+
start: ['min', yBaseline],
35+
end: [xBaseline, 'min'],
36+
},
37+
style: {
38+
fill: '#d8d0c0',
39+
opacity: 0.4,
40+
},
41+
},
42+
{
43+
position: {
44+
start: [xBaseline, yBaseline],
45+
end: ['max', 'min'],
46+
},
47+
style: {
48+
fill: '#a3dda1',
49+
opacity: 0.4,
50+
},
51+
},
52+
],
53+
lineStyle: {
54+
stroke: '#9ba29a',
55+
lineWidth: 1,
56+
},
57+
labelStyle: [
58+
{
59+
position: ['max', yBaseline],
60+
offsetX: -textOffset,
61+
offsetY: -textOffset,
62+
style: {
63+
textAlign: 'right',
64+
textBaseline: 'bottom',
65+
fontSize: 14,
66+
fill: '#ccc',
67+
},
68+
},
69+
{
70+
position: ['min', yBaseline],
71+
offsetX: textOffset,
72+
offsetY: -textOffset,
73+
style: {
74+
textAlign: 'left',
75+
textBaseline: 'bottom',
76+
fontSize: 14,
77+
fill: '#ccc',
78+
},
79+
},
80+
{
81+
position: ['min', yBaseline],
82+
offsetX: textOffset,
83+
offsetY: textOffset,
84+
style: {
85+
textAlign: 'left',
86+
textBaseline: 'top',
87+
fontSize: 14,
88+
fill: '#ccc',
89+
},
90+
},
91+
{
92+
position: ['max', yBaseline],
93+
offsetX: -textOffset,
94+
offsetY: textOffset,
95+
style: {
96+
textAlign: 'right',
97+
textBaseline: 'top',
98+
fontSize: 14,
99+
fill: '#ccc',
100+
},
101+
},
102+
],
103+
};
104+
return defaultConfig;
105+
}

src/types/common.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AnnotationPosition, RegionPositionBaseOption, TextOption } from '@antv/g2/lib/interface';
12
import { Axis } from './axis';
23
import { Label } from './label';
34
import { Tooltip } from './tooltip';
@@ -7,6 +8,9 @@ import { Animation } from './animation';
78
import { State } from './state';
89
import { Slider } from './slider';
910

11+
/** annotation position */
12+
export { AnnotationPosition, RegionPositionBaseOption, TextOption };
13+
1014
/** 一条数据记录 */
1115
export type Datum = Record<string, any>;
1216

0 commit comments

Comments
 (0)