Skip to content

Commit 9f0a25e

Browse files
authored
(feat) 回归散点图增加显示回归拟合函数相关配置,close #3474 (#3476)
1 parent 0e7530d commit 9f0a25e

File tree

7 files changed

+391
-24
lines changed

7 files changed

+391
-24
lines changed

__tests__/unit/plots/scatter/regression-line-spec.ts

+185
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,189 @@ describe('scatter', () => {
226226
expect(scatter.chart.getComponents().find((item) => item.type === 'annotation')?.layer).toBe('bg');
227227
scatter.destroy();
228228
});
229+
230+
it('regressionLine: showEquation: default(false)', async () => {
231+
const scatter = new Scatter(createDiv('regressionLine'), {
232+
data,
233+
width: 400,
234+
height: 300,
235+
xField: 'x',
236+
yField: 'y',
237+
animation: false,
238+
regressionLine: {
239+
type: 'linear',
240+
},
241+
});
242+
243+
scatter.render();
244+
245+
const annotationShapes = scatter?.chart
246+
?.getComponents()
247+
?.find((item) => item.type === 'annotation')
248+
?.component.cfg.group.cfg.children[0].getChildren();
249+
const regressionLineShape = annotationShapes?.[0];
250+
expect(regressionLineShape).not.toBeNull();
251+
const equationShape = annotationShapes?.find((shape) => shape?.cfg?.name === 'regression-equation');
252+
expect(equationShape).toBeUndefined();
253+
254+
scatter.destroy();
255+
});
256+
257+
it('regressionLine: showEquation: true', async () => {
258+
const scatter = new Scatter(createDiv('regressionLine'), {
259+
data,
260+
width: 400,
261+
height: 300,
262+
xField: 'x',
263+
yField: 'y',
264+
animation: false,
265+
regressionLine: {
266+
type: 'linear',
267+
showEquation: true,
268+
},
269+
});
270+
271+
scatter.render();
272+
273+
const annotationShapes = scatter?.chart
274+
?.getComponents()
275+
?.find((item) => item.type === 'annotation')
276+
?.component.cfg.group.cfg.children[0].getChildren();
277+
const equationShape = annotationShapes?.find((shape) => shape?.cfg?.name === 'regression-equation');
278+
279+
expect(equationShape).not.toBeUndefined();
280+
expect(equationShape?.attrs?.text).toMatch(/^y =/);
281+
282+
scatter.destroy();
283+
});
284+
285+
it('regressionLine: equation string with customize algorithm', async () => {
286+
const mockEquation = 'y = x';
287+
const scatter = new Scatter(createDiv('regressionLine'), {
288+
data,
289+
width: 400,
290+
height: 300,
291+
xField: 'x',
292+
yField: 'y',
293+
animation: false,
294+
regressionLine: {
295+
type: 'linear',
296+
showEquation: true,
297+
equation: mockEquation,
298+
algorithm: [
299+
[1, 1],
300+
[2, 2],
301+
[3, 3],
302+
],
303+
},
304+
});
305+
306+
scatter.render();
307+
308+
const annotationShapes = scatter?.chart
309+
?.getComponents()
310+
?.find((item) => item.type === 'annotation')
311+
?.component.cfg.group.cfg.children[0].getChildren();
312+
const equationShape = annotationShapes?.find((shape) => shape?.cfg?.name === 'regression-equation');
313+
314+
expect(equationShape).not.toBeUndefined();
315+
expect(equationShape?.attrs?.text).toBe(mockEquation);
316+
317+
scatter.destroy();
318+
});
319+
320+
it('regressionLine: equation string without customize algorithm', async () => {
321+
const mockEquation = 'y = x';
322+
const scatter = new Scatter(createDiv('regressionLine'), {
323+
data,
324+
width: 400,
325+
height: 300,
326+
xField: 'x',
327+
yField: 'y',
328+
animation: false,
329+
regressionLine: {
330+
type: 'linear',
331+
showEquation: true,
332+
equation: mockEquation,
333+
},
334+
});
335+
336+
scatter.render();
337+
338+
const annotationShapes = scatter?.chart
339+
?.getComponents()
340+
?.find((item) => item.type === 'annotation')
341+
?.component.cfg.group.cfg.children[0].getChildren();
342+
const equationShape = annotationShapes?.find((shape) => shape?.cfg?.name === 'regression-equation');
343+
344+
expect(equationShape).not.toBeUndefined();
345+
expect(equationShape?.attrs?.text).not.toBe(mockEquation);
346+
347+
scatter.destroy();
348+
});
349+
350+
it('regressionLine: equation string and disabled showEquation', async () => {
351+
const mockEquation = 'y = x';
352+
const scatter = new Scatter(createDiv('regressionLine'), {
353+
data,
354+
width: 400,
355+
height: 300,
356+
xField: 'x',
357+
yField: 'y',
358+
animation: false,
359+
regressionLine: {
360+
type: 'linear',
361+
showEquation: false,
362+
equation: mockEquation,
363+
algorithm: [
364+
[1, 1],
365+
[2, 2],
366+
[3, 3],
367+
],
368+
},
369+
});
370+
371+
scatter.render();
372+
373+
const annotationShapes = scatter?.chart
374+
?.getComponents()
375+
?.find((item) => item.type === 'annotation')
376+
?.component.cfg.group.cfg.children[0].getChildren();
377+
const equationShape = annotationShapes?.find((shape) => shape?.cfg?.name === 'regression-equation');
378+
379+
expect(equationShape).toBeUndefined();
380+
381+
scatter.destroy();
382+
});
383+
384+
it('regressionLine: equationStyle', async () => {
385+
const scatter = new Scatter(createDiv('regressionLine'), {
386+
data,
387+
width: 400,
388+
height: 300,
389+
xField: 'x',
390+
yField: 'y',
391+
animation: false,
392+
regressionLine: {
393+
type: 'linear',
394+
showEquation: true,
395+
equationStyle: {
396+
fontSize: 24,
397+
},
398+
},
399+
});
400+
401+
scatter.render();
402+
403+
const annotationShapes = scatter?.chart
404+
?.getComponents()
405+
?.find((item) => item.type === 'annotation')
406+
?.component.cfg.group.cfg.children[0].getChildren();
407+
const equationShape = annotationShapes?.find((shape) => shape?.cfg?.name === 'regression-equation');
408+
409+
expect(equationShape).not.toBeUndefined();
410+
expect(equationShape?.attrs?.fontSize).toBe(24);
411+
412+
scatter.destroy();
413+
});
229414
});
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { getRegressionEquation } from '../../../../src/plots/scatter/util';
2+
3+
describe('scatter util', () => {
4+
describe('equation format tests', () => {
5+
it('should get linear equation', () => {
6+
const type = 'linear';
7+
const equation = getRegressionEquation(type, { a: 0.1, b: 0.5, rSquared: 0.92 });
8+
expect(equation).toBe('y = 0.1x + 0.5, R^2 = 0.92');
9+
});
10+
11+
it('should get linear equation with nagtive coefficient', () => {
12+
const type = 'linear';
13+
const equation = getRegressionEquation(type, { a: -0.1, b: -0.5, rSquared: 0 });
14+
expect(equation).toBe('y = -0.1x + -0.5, R^2 = 0');
15+
});
16+
17+
it('should get qustion mark when coefficient is not a number', () => {
18+
const type = 'linear';
19+
const equation = getRegressionEquation(type, { a: NaN, b: Infinity, rSquared: undefined });
20+
expect(equation).toBe('y = ?x + ?, R^2 = ?');
21+
});
22+
23+
it('should get exponential equation', () => {
24+
const type = 'exp';
25+
const equation = getRegressionEquation(type, { a: 0.1, b: 0.5, rSquared: 0.92 });
26+
expect(equation).toBe('y = 0.1e^(0.5x), R^2 = 0.92');
27+
});
28+
29+
it('should get quadratic equation', () => {
30+
const type = 'quad';
31+
const equation = getRegressionEquation(type, { a: 0.1, b: 0.5, c: 1, rSquared: 0.92 });
32+
expect(equation).toBe('y = 0.1x^2 + 0.5x + 1, R^2 = 0.92');
33+
});
34+
35+
it('should get polynomial equation', () => {
36+
const type = 'poly';
37+
const equation = getRegressionEquation(type, { coefficients: [0.1, 0.2, 0.3], rSquared: 0.92 });
38+
expect(equation).toBe('y = 0.1 + 0.2x + 0.3x^2, R^2 = 0.92');
39+
});
40+
41+
it('should get polynomial equation with more coefficients', () => {
42+
const type = 'poly';
43+
const equation = getRegressionEquation(type, { coefficients: [0.1, 0.2, 0.3, 0.4, 0.5], rSquared: 0.92 });
44+
expect(equation).toBe('y = 0.1 + 0.2x + 0.3x^2 + 0.4x^3 + 0.5x^4, R^2 = 0.92');
45+
});
46+
47+
it('should get power law equation', () => {
48+
const type = 'pow';
49+
const equation = getRegressionEquation(type, { a: 0.1, b: 2, rSquared: 0.92 });
50+
expect(equation).toBe('y = 0.1x^2, R^2 = 0.92');
51+
});
52+
53+
it('should get loess equation', () => {
54+
const type = 'loess';
55+
const equation = getRegressionEquation(type, { a: 0.1, b: 2, rSquared: 0.92 });
56+
expect(equation).toBeNull();
57+
});
58+
59+
it('should round number by default presicion', () => {
60+
const type = 'linear';
61+
const equation = getRegressionEquation(type, { a: -0.1234567, b: -0.12345678, rSquared: 0.12345678 });
62+
expect(equation).toBe('y = -0.1235x + -0.1235, R^2 = 0.1235');
63+
});
64+
65+
it('should round polynomial equation with more than 3 coefficients', () => {
66+
const type = 'poly';
67+
const equation = getRegressionEquation(type, {
68+
coefficients: [0.1234567, 0.1234567, 0.1234567, 0.1234567, 0.1234567],
69+
rSquared: 0.1234567,
70+
});
71+
expect(equation).toBe('y = 0.1235 + 0.1235x + 0.1235x^2 + 0.1235x^3 + 0.1235x^4, R^2 = 0.1235');
72+
});
73+
});
74+
});

site/docs/api/plots/scatter.en.md

+19-9
Original file line numberDiff line numberDiff line change
@@ -267,22 +267,32 @@ Quadrant components.
267267

268268
Regression line.
269269

270-
| Properties | Type | Description |
271-
| ---------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
272-
| type | string | The type of regression line, exp \| linear \| loess \| log \| poly \| pow \| quad |
273-
| style | object | Configure the regression line style. Configure the reference drawing properties for details |
274-
| algorithm | Array<[number, number]> \| ((data: any) => Array<[number, number]>) | Custom algorithm with a higher priority than type |
275-
| top | boolean | Whether top level display |
270+
| Properties | Type | Description |
271+
|---------------|---------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
272+
| type | string | The type of regression line, exp \| linear \| loess \| log \| poly \| pow \| quad |
273+
| style | object | Configure the regression line style. Configure the reference drawing properties for details |
274+
| algorithm | Array<[number, number]> \| ((data: any) => Array<[number, number]>) | Custom algorithm with a higher priority than type |
275+
| top | boolean | Whether top level display |
276+
| showEquation | boolean | Whether to display regression equation, default is hidden |
277+
| equation | string | Custom algorithm equation, only when enable showEquation and custom algorithm property |
278+
| equationStyle | _TextStyle_ | Custom equation text style |
279+
280+
其中,**_TextStyle_** 类型定义详见: [配置文字样式](/en/docs/api/graphic-style)
276281

277282
```ts
278283
regressionLine: {
279284
algorithm: () => {
280285
return [
281-
[8, 6],
282-
[16, 7],
283-
[24, 7],
286+
[1, 3],
287+
[4, 9],
288+
[7, 15],
284289
];
285290
},
291+
showEquation: true,
292+
equation: 'y = 2x + 1',
293+
equationStyle: {
294+
fontSize: 20
295+
}
286296
}
287297
```
288298

site/docs/api/plots/scatter.zh.md

+22-11
Original file line numberDiff line numberDiff line change
@@ -266,22 +266,32 @@ xAxis、yAxis 配置相同。**注意**:由于 DualAxes(双轴图) 和 Bidirec
266266

267267
回归线。
268268

269-
| 细分配置 | 类型 | 功能描述 |
270-
| --------- | ------------------------------------------------------------------- | ---------------------------------------------------------------- |
271-
| type | string | 回归线类型, exp \| linear \| loess \| log \| poly \| pow \| quad |
272-
| style | object | 配置回归线样式,详细配置参考绘图属性 |
273-
| algorithm | Array<[number, number]> \| ((data: any) => Array<[number, number]>) | 自定义算法,优先级高于 type |
274-
| top | boolean | 是否顶层显示 |
269+
| 细分配置 | 类型 | 功能描述 |
270+
|---------------|---------------------------------------------------------------------|--------------------------------------------------------------------|
271+
| type | string | 回归线类型, exp \| linear \| loess \| log \| poly \| pow \| quad |
272+
| style | object | 配置回归线样式,详细配置参考绘图属性 |
273+
| algorithm | Array<[number, number]> \| ((data: any) => Array<[number, number]>) | 自定义算法,优先级高于 type |
274+
| top | boolean | 是否顶层显示 |
275+
| showEquation | boolean | 显示回归方程式,默认不显示 |
276+
| equation | string | 自定义回归方程式,仅在已经自定义 algorithm 以及启用 showEquation 时生效 |
277+
| equationStyle | _TextStyle_ | 自定义回归方程式文字样式 |
278+
279+
其中,**_TextStyle_** 类型定义详见: [配置文字样式](/zh/docs/api/graphic-style)
275280

276281
```ts
277282
regressionLine: {
278283
algorithm: () => {
279284
return [
280-
[8, 6],
281-
[16, 7],
282-
[24, 7],
285+
[1, 3],
286+
[4, 9],
287+
[7, 15],
283288
];
284289
},
290+
showEquation: true,
291+
equation: 'y = 2x + 1',
292+
equationStyle: {
293+
fontSize: 20
294+
}
285295
}
286296
```
287297

@@ -301,13 +311,14 @@ regressionLine: {
301311

302312
散点图内置了一些交互,列表如下:
303313

304-
| 交互 | 描述 | 配置 |
305-
| ----------- | ---------------------------------------- | ------------------------------ |
314+
| 交互 | 描述 | 配置 |
315+
| ----- | ---------------------------------------- | -------------------------- |
306316
| brush | 用于刷选交互,配置该交互后,可进行刷选。 | `brush: { enabled: true }` |
307317

308318
<embed src="@/docs/common/brush.zh.md"></embed>
309319

310320
<embed src="@/docs/common/interactions.zh.md"></embed>
311321

312322
<!-- 直接 三级导航展开 -->
323+
313324
<embed src="@/docs/common/annotations.zh.md"></embed>

0 commit comments

Comments
 (0)