Skip to content

Commit 598dd02

Browse files
committed
refactor(gauge): 仪表盘改造, 米轨绘制方案改造 & 单测
1 parent 0e8de05 commit 598dd02

File tree

4 files changed

+90
-141
lines changed

4 files changed

+90
-141
lines changed

__tests__/unit/plots/gauge/shapes/index-spec.ts

+33-46
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IGroup } from '@antv/g2';
22
import { Gauge } from '../../../../../src';
3-
import { MASK_VIEW_ID } from '../../../../../src/plots/gauge/constants';
43
import { createDiv } from '../../../../utils/dom';
4+
import { getPixelColor } from '../../../../utils/getPixelColor';
55

66
describe('gauge', () => {
77
it('no indicator', async () => {
@@ -92,15 +92,20 @@ describe('gauge', () => {
9292

9393
gauge.render();
9494

95-
expect(gauge.chart.views.length).toBe(2);
96-
95+
expect(gauge.chart.views.length).toBe(1);
9796
gauge.update({ indicator: {} });
98-
expect(gauge.chart.views.length).toBe(3);
99-
expect(gauge.chart.views[2].id).toBe(MASK_VIEW_ID);
97+
expect(gauge.chart.views.length).toBe(2);
10098

10199
gauge.destroy();
102100
});
103101

102+
function getAllShapes(view) {
103+
return view.geometries[0].elements.reduce((r, ele) => {
104+
r.push(...(ele.shape as IGroup).getChildren());
105+
return r;
106+
}, []);
107+
}
108+
104109
it('meter gauge: custom steps', () => {
105110
const gauge = new Gauge(createDiv(), {
106111
type: 'meter',
@@ -110,10 +115,18 @@ describe('gauge', () => {
110115
});
111116

112117
gauge.render();
113-
114-
expect(gauge.chart.views.length).toBe(2);
115-
const maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
116-
expect(maskShape.getChildren().length).toBe(99);
118+
expect(gauge.chart.views.length).toBe(1);
119+
expect(getAllShapes(gauge.chart.views[0]).length).toBe(100);
120+
121+
for (let i = 0; i < 10; i++) {
122+
const steps = (100 * Math.random()) | 0;
123+
gauge.update({ meter: { steps }, range: { ticks: [0, 1 / 3, 2 / 3, 1] } });
124+
// 存在交接
125+
expect(getAllShapes(gauge.chart.views[0]).length).toBeGreaterThanOrEqual(steps);
126+
// 不存在交接
127+
gauge.update({ meter: { steps }, range: { ticks: [0, 1] } });
128+
expect(getAllShapes(gauge.chart.views[0]).length).toBe(steps);
129+
}
117130

118131
gauge.destroy();
119132
});
@@ -123,26 +136,21 @@ describe('gauge', () => {
123136
type: 'meter',
124137
percent: 0.75,
125138
indicator: null,
139+
range: { ticks: [0, 1] },
126140
meter: { steps: 100, stepRatio: 1 },
127141
});
128142

129143
gauge.render();
130-
131-
expect(gauge.chart.views.length).toBe(2);
132-
let maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
133-
expect(maskShape.getChildren().length).toBe(0);
144+
expect(getAllShapes(gauge.chart.views[0]).length).toBe(1);
134145

135146
gauge.update({ meter: { steps: 40, stepRatio: 0.2 } });
136-
maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
137-
expect(maskShape.getChildren().length).toBe(39);
147+
expect(getAllShapes(gauge.chart.views[0]).length).toBe(40);
138148

139149
gauge.update({ meter: { stepRatio: 1.2 } });
140-
maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
141-
expect(maskShape.getChildren().length).toBe(0);
150+
expect(getAllShapes(gauge.chart.views[0]).length).toBe(1);
142151

143152
gauge.update({ meter: { stepRatio: -0.2 } });
144-
maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
145-
expect(maskShape.getChildren().length).toBe(0);
153+
expect(getAllShapes(gauge.chart.views[0]).length).toBe(1);
146154

147155
gauge.destroy();
148156
});
@@ -157,37 +165,16 @@ describe('gauge', () => {
157165

158166
gauge.render();
159167

160-
expect(gauge.chart.views.length).toBe(2);
161-
const maskShape1 = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
162-
expect(maskShape1.getChildren().length).toBe(99);
168+
expect(gauge.chart.views.length).toBe(1);
169+
expect(getAllShapes(gauge.chart.views[0]).length).toBeGreaterThanOrEqual(100);
163170

164-
const shape1 = maskShape1.getChildren()[0].getBBox();
171+
const bbox1 = getAllShapes(gauge.chart.views[0])[0].getBBox();
165172

166173
gauge.update({ indicator: {} });
167-
expect(gauge.chart.views.length).toBe(3);
168-
const maskShape2 = gauge.chart.views[2].geometries[0].elements[0].shape as IGroup;
169-
expect(shape1).toEqual(maskShape2.getChildren()[0].getBBox());
170-
171-
gauge.destroy();
172-
});
173-
174-
it('meter gauge: custom theme.background', () => {
175-
const gauge = new Gauge(createDiv(), {
176-
type: 'meter',
177-
percent: 0.75,
178-
indicator: null,
179-
theme: { background: 'red' },
180-
});
181-
182-
gauge.render();
183-
174+
// indicator 绘制在 view0
175+
const group2 = getAllShapes(gauge.chart.views[1])[0];
184176
expect(gauge.chart.views.length).toBe(2);
185-
let maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
186-
expect(maskShape.getChildren()[0].attr('fill')).toBe('red');
187-
188-
gauge.update({ theme: 'dark' });
189-
maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
190-
expect(maskShape.getChildren()[0].attr('fill')).toBe(gauge.chart.getTheme().background);
177+
expect(bbox1).toEqual(group2.getBBox());
191178

192179
gauge.destroy();
193180
});

src/plots/gauge/adaptor.ts

+11-49
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1+
import { Geometry } from '@antv/g2';
12
import { isString } from '@antv/util';
23
import { interaction, animation, theme, scale, annotation } from '../../adaptor/common';
34
import { interval } from '../../adaptor/geometries';
45
import { AXIS_META_CONFIG_KEYS } from '../../constant';
56
import { Params } from '../../core/adaptor';
67
import { deepAssign, flow, pick, renderGaugeStatistic } from '../../utils';
7-
import {
8-
RANGE_TYPE,
9-
RANGE_VALUE,
10-
PERCENT,
11-
DEFAULT_COLOR,
12-
INDICATEOR_VIEW_ID,
13-
RANGE_VIEW_ID,
14-
MASK_VIEW_ID,
15-
} from './constants';
16-
import { GaugeCustomInfo, GaugeOptions } from './types';
8+
import { RANGE_TYPE, RANGE_VALUE, PERCENT, DEFAULT_COLOR, INDICATEOR_VIEW_ID, RANGE_VIEW_ID } from './constants';
9+
import { GaugeOptions } from './types';
1710
import { getIndicatorData, getRangeData } from './utils';
1811

1912
/**
@@ -22,7 +15,8 @@ import { getIndicatorData, getRangeData } from './utils';
2215
*/
2316
function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
2417
const { chart, options } = params;
25-
const { percent, range, radius, innerRadius, startAngle, endAngle, axis, indicator, gaugeStyle } = options;
18+
const { percent, range, radius, innerRadius, startAngle, endAngle, axis, indicator, gaugeStyle, type, meter } =
19+
options;
2620
const { color, width: rangeWidth } = range;
2721

2822
// 指标 & 指针
@@ -61,7 +55,7 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
6155

6256
const rangeColor = isString(color) ? [color, DEFAULT_COLOR] : color;
6357

64-
interval({
58+
const { ext } = interval({
6559
chart: v2,
6660
options: {
6761
xField: '1',
@@ -72,6 +66,7 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
7266
interval: {
7367
color: rangeColor,
7468
style: gaugeStyle,
69+
shape: type === 'meter' ? 'meter-gauge' : null,
7570
},
7671
args: {
7772
zIndexReversed: true,
@@ -81,6 +76,10 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
8176
},
8277
});
8378

79+
const geometry = ext.geometry as Geometry;
80+
// 传入到自定义 shape 中
81+
geometry.customInfo({ meter });
82+
8483
v2.coordinate('polar', {
8584
innerRadius,
8685
radius,
@@ -91,41 +90,6 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
9190
return params;
9291
}
9392

94-
/**
95-
* meter 类型的仪表盘 有一层 mask
96-
* @param params
97-
*/
98-
function meterView(params: Params<GaugeOptions>): Params<GaugeOptions> {
99-
const { chart, options } = params;
100-
101-
const { type, meter } = options;
102-
if (type === 'meter') {
103-
const { innerRadius, radius, startAngle, endAngle, range } = options;
104-
const minColumnWidth = range?.width;
105-
const maxColumnWidth = range?.width;
106-
107-
const { background } = chart.getTheme();
108-
109-
let color = background;
110-
if (!color || color === 'transparent') {
111-
color = '#fff';
112-
}
113-
114-
const v3 = chart.createView({ id: MASK_VIEW_ID });
115-
v3.data([{ [RANGE_TYPE]: '1', [RANGE_VALUE]: 1 }]);
116-
const customInfo: GaugeCustomInfo = { meter };
117-
v3.interval({ minColumnWidth, maxColumnWidth })
118-
.position(`1*${RANGE_VALUE}`)
119-
.color(color)
120-
.adjust('stack')
121-
.shape('meter-gauge')
122-
.customInfo(customInfo);
123-
v3.coordinate('polar', { innerRadius, radius, startAngle, endAngle }).transpose();
124-
}
125-
126-
return params;
127-
}
128-
12993
/**
13094
* meta 配置
13195
* @param params
@@ -217,8 +181,6 @@ export function adaptor(params: Params<GaugeOptions>) {
217181
meta,
218182
statistic,
219183
interaction,
220-
// meterView 需要放到主题之后
221-
meterView,
222184
annotation(),
223185
other
224186
// ... 其他的 adaptor flow

src/plots/gauge/constants.ts

-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ export const DEFAULT_COLOR = '#f0f0f0';
88
export const INDICATEOR_VIEW_ID = 'indicator-view';
99
export const RANGE_VIEW_ID = 'range-view';
1010

11-
/** meter 类型的仪表盘 带 mask 的 view */
12-
export const MASK_VIEW_ID = 'range-mask-view';
13-
1411
/**
1512
* 仪表盘默认配置项
1613
*/

src/plots/gauge/shapes/meter-gauge.ts

+46-43
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,65 @@
11
import { registerShape, Types, Util } from '@antv/g2';
2+
import { clamp } from '@antv/util';
23
import { GaugeCustomInfo } from '../types';
34

45
type ShapeCfg = Omit<Types.ShapeInfo, 'customInfo'> & {
56
customInfo: GaugeCustomInfo;
67
};
78

8-
// 自定义Shape 部分
9+
/**
10+
* 自定义 Shape 部分: 自定义米轨仪表盘
11+
* 定义 STEP, STEP_RATIO. 可绘制区域: 1 / (STEP + 1) * i -> 1 / (STEP + 1) * i + (STEP_RATIO / (STEP + 1))
12+
*/
913
registerShape('interval', 'meter-gauge', {
1014
draw(cfg: ShapeCfg, container) {
1115
// 使用 customInfo 传递参数
1216
const { meter = {} } = cfg.customInfo;
13-
const { steps: STEP = 50, stepRatio = 0.5 } = meter;
17+
let { steps: STEP = 50, stepRatio: STEP_RATIO = 0.5 } = meter;
18+
STEP = STEP < 1 ? 1 : STEP;
19+
// stepRatio 取值范围: (0, 1]
20+
STEP_RATIO = clamp(STEP_RATIO, 0, 1);
1421

15-
const total = this.coordinate.endAngle - this.coordinate.startAngle;
16-
let interval = total / STEP;
17-
let gap = 0;
18-
19-
/**
20-
* stepRatio 取值范围: (0, 1]
21-
* 1: interval : gap = stepRatio : (1 - stepRatio)
22-
* 2: interval * STEP + stepRatio * (STEP - 1) = total
23-
*/
24-
if (stepRatio > 0 && stepRatio <= 1) {
25-
interval = total / (((1 - stepRatio) / stepRatio) * (STEP - 1) + STEP);
26-
gap = (interval * (1 - stepRatio)) / stepRatio;
22+
const { startAngle: COORD_START_ANGLE, endAngle: COORD_END_ANGLE } = this.coordinate;
23+
let GAP = 0;
24+
if (STEP_RATIO > 0 && STEP_RATIO < 1) {
25+
const TOTAL = COORD_END_ANGLE - COORD_START_ANGLE;
26+
GAP = TOTAL / STEP / (STEP_RATIO / (1 - STEP_RATIO) + 1 - 1 / STEP);
2727
}
28+
const INTERVAL = (GAP / (1 - STEP_RATIO)) * STEP_RATIO;
2829

2930
const group = container.addGroup();
30-
// 绘制 gap
31-
if (gap > 0) {
32-
const center = this.coordinate.getCenter();
33-
const radius = this.coordinate.getRadius();
34-
const { startAngle, endAngle } = Util.getAngle(cfg, this.coordinate);
35-
for (let i = startAngle, j = 0; i < endAngle && j < 2 * STEP - 1; j++) {
36-
const drawn = j % 2;
37-
if (drawn) {
38-
const path = Util.getSectorPath(
39-
center.x,
40-
center.y,
41-
radius,
42-
i,
43-
Math.min(i + gap, endAngle),
44-
radius * this.coordinate.innerRadius
45-
);
46-
group.addShape('path', {
47-
name: 'meter-gauge-mask',
48-
attrs: {
49-
path,
50-
fill: cfg.color,
51-
stroke: cfg.color,
52-
lineWidth: 0.5,
53-
},
54-
// mask 不需要捕捉事件
55-
capture: false,
56-
});
57-
}
58-
i += drawn ? gap : interval;
31+
// 绘制图形的时候,留下 gap
32+
const center = this.coordinate.getCenter();
33+
const radius = this.coordinate.getRadius();
34+
const { startAngle: START_ANGLE, endAngle: END_ANGLE } = Util.getAngle(cfg, this.coordinate);
35+
36+
for (let startAngle = START_ANGLE; startAngle < END_ANGLE; ) {
37+
let endAngle;
38+
const r = (startAngle - COORD_START_ANGLE) % (INTERVAL + GAP);
39+
if (r < INTERVAL) {
40+
endAngle = startAngle + (INTERVAL - r);
41+
} else {
42+
startAngle += INTERVAL + GAP - r;
43+
endAngle = startAngle + INTERVAL;
5944
}
45+
const path = Util.getSectorPath(
46+
center.x,
47+
center.y,
48+
radius,
49+
startAngle,
50+
Math.min(endAngle, END_ANGLE),
51+
radius * this.coordinate.innerRadius
52+
);
53+
group.addShape('path', {
54+
name: 'meter-gauge',
55+
attrs: {
56+
path,
57+
fill: cfg.color,
58+
stroke: cfg.color,
59+
lineWidth: 0.5,
60+
},
61+
});
62+
startAngle = endAngle + GAP;
6063
}
6164

6265
return group;

0 commit comments

Comments
 (0)