Skip to content

Commit

Permalink
refactor(gauge): 仪表盘改造, 米轨绘制方案改造 & 单测 (#2890)
Browse files Browse the repository at this point in the history
* refactor(gauge): 仪表盘改造, 米轨绘制方案改造 & 单测

* fix: remove unused variable
  • Loading branch information
visiky committed Oct 8, 2021
1 parent 6a0512b commit cc8fa33
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 141 deletions.
78 changes: 32 additions & 46 deletions __tests__/unit/plots/gauge/shapes/index-spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { IGroup } from '@antv/g2';
import { Gauge } from '../../../../../src';
import { MASK_VIEW_ID } from '../../../../../src/plots/gauge/constants';
import { createDiv } from '../../../../utils/dom';

describe('gauge', () => {
Expand Down Expand Up @@ -92,15 +91,20 @@ describe('gauge', () => {

gauge.render();

expect(gauge.chart.views.length).toBe(2);

expect(gauge.chart.views.length).toBe(1);
gauge.update({ indicator: {} });
expect(gauge.chart.views.length).toBe(3);
expect(gauge.chart.views[2].id).toBe(MASK_VIEW_ID);
expect(gauge.chart.views.length).toBe(2);

gauge.destroy();
});

function getAllShapes(view) {
return view.geometries[0].elements.reduce((r, ele) => {
r.push(...(ele.shape as IGroup).getChildren());
return r;
}, []);
}

it('meter gauge: custom steps', () => {
const gauge = new Gauge(createDiv(), {
type: 'meter',
Expand All @@ -110,10 +114,18 @@ describe('gauge', () => {
});

gauge.render();

expect(gauge.chart.views.length).toBe(2);
const maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
expect(maskShape.getChildren().length).toBe(99);
expect(gauge.chart.views.length).toBe(1);
expect(getAllShapes(gauge.chart.views[0]).length).toBe(100);

for (let i = 0; i < 10; i++) {
const steps = (100 * Math.random()) | 0;
gauge.update({ meter: { steps }, range: { ticks: [0, 1 / 3, 2 / 3, 1] } });
// 存在交接
expect(getAllShapes(gauge.chart.views[0]).length).toBeGreaterThanOrEqual(steps);
// 不存在交接
gauge.update({ meter: { steps }, range: { ticks: [0, 1] } });
expect(getAllShapes(gauge.chart.views[0]).length).toBe(steps);
}

gauge.destroy();
});
Expand All @@ -123,26 +135,21 @@ describe('gauge', () => {
type: 'meter',
percent: 0.75,
indicator: null,
range: { ticks: [0, 1] },
meter: { steps: 100, stepRatio: 1 },
});

gauge.render();

expect(gauge.chart.views.length).toBe(2);
let maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
expect(maskShape.getChildren().length).toBe(0);
expect(getAllShapes(gauge.chart.views[0]).length).toBe(1);

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

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

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

gauge.destroy();
});
Expand All @@ -157,37 +164,16 @@ describe('gauge', () => {

gauge.render();

expect(gauge.chart.views.length).toBe(2);
const maskShape1 = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
expect(maskShape1.getChildren().length).toBe(99);
expect(gauge.chart.views.length).toBe(1);
expect(getAllShapes(gauge.chart.views[0]).length).toBeGreaterThanOrEqual(100);

const shape1 = maskShape1.getChildren()[0].getBBox();
const bbox1 = getAllShapes(gauge.chart.views[0])[0].getBBox();

gauge.update({ indicator: {} });
expect(gauge.chart.views.length).toBe(3);
const maskShape2 = gauge.chart.views[2].geometries[0].elements[0].shape as IGroup;
expect(shape1).toEqual(maskShape2.getChildren()[0].getBBox());

gauge.destroy();
});

it('meter gauge: custom theme.background', () => {
const gauge = new Gauge(createDiv(), {
type: 'meter',
percent: 0.75,
indicator: null,
theme: { background: 'red' },
});

gauge.render();

// indicator 绘制在 view0
const group2 = getAllShapes(gauge.chart.views[1])[0];
expect(gauge.chart.views.length).toBe(2);
let maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
expect(maskShape.getChildren()[0].attr('fill')).toBe('red');

gauge.update({ theme: 'dark' });
maskShape = gauge.chart.views[1].geometries[0].elements[0].shape as IGroup;
expect(maskShape.getChildren()[0].attr('fill')).toBe(gauge.chart.getTheme().background);
expect(bbox1).toEqual(group2.getBBox());

gauge.destroy();
});
Expand Down
60 changes: 11 additions & 49 deletions src/plots/gauge/adaptor.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import { Geometry } from '@antv/g2';
import { isString } from '@antv/util';
import { interaction, animation, theme, scale, annotation } from '../../adaptor/common';
import { interval } from '../../adaptor/geometries';
import { AXIS_META_CONFIG_KEYS } from '../../constant';
import { Params } from '../../core/adaptor';
import { deepAssign, flow, pick, renderGaugeStatistic } from '../../utils';
import {
RANGE_TYPE,
RANGE_VALUE,
PERCENT,
DEFAULT_COLOR,
INDICATEOR_VIEW_ID,
RANGE_VIEW_ID,
MASK_VIEW_ID,
} from './constants';
import { GaugeCustomInfo, GaugeOptions } from './types';
import { RANGE_TYPE, RANGE_VALUE, PERCENT, DEFAULT_COLOR, INDICATEOR_VIEW_ID, RANGE_VIEW_ID } from './constants';
import { GaugeOptions } from './types';
import { getIndicatorData, getRangeData } from './utils';

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

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

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

interval({
const { ext } = interval({
chart: v2,
options: {
xField: '1',
Expand All @@ -72,6 +66,7 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
interval: {
color: rangeColor,
style: gaugeStyle,
shape: type === 'meter' ? 'meter-gauge' : null,
},
args: {
zIndexReversed: true,
Expand All @@ -81,6 +76,10 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
},
});

const geometry = ext.geometry as Geometry;
// 传入到自定义 shape 中
geometry.customInfo({ meter });

v2.coordinate('polar', {
innerRadius,
radius,
Expand All @@ -91,41 +90,6 @@ function geometry(params: Params<GaugeOptions>): Params<GaugeOptions> {
return params;
}

/**
* meter 类型的仪表盘 有一层 mask
* @param params
*/
function meterView(params: Params<GaugeOptions>): Params<GaugeOptions> {
const { chart, options } = params;

const { type, meter } = options;
if (type === 'meter') {
const { innerRadius, radius, startAngle, endAngle, range } = options;
const minColumnWidth = range?.width;
const maxColumnWidth = range?.width;

const { background } = chart.getTheme();

let color = background;
if (!color || color === 'transparent') {
color = '#fff';
}

const v3 = chart.createView({ id: MASK_VIEW_ID });
v3.data([{ [RANGE_TYPE]: '1', [RANGE_VALUE]: 1 }]);
const customInfo: GaugeCustomInfo = { meter };
v3.interval({ minColumnWidth, maxColumnWidth })
.position(`1*${RANGE_VALUE}`)
.color(color)
.adjust('stack')
.shape('meter-gauge')
.customInfo(customInfo);
v3.coordinate('polar', { innerRadius, radius, startAngle, endAngle }).transpose();
}

return params;
}

/**
* meta 配置
* @param params
Expand Down Expand Up @@ -217,8 +181,6 @@ export function adaptor(params: Params<GaugeOptions>) {
meta,
statistic,
interaction,
// meterView 需要放到主题之后
meterView,
annotation(),
other
// ... 其他的 adaptor flow
Expand Down
3 changes: 0 additions & 3 deletions src/plots/gauge/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ export const DEFAULT_COLOR = '#f0f0f0';
export const INDICATEOR_VIEW_ID = 'indicator-view';
export const RANGE_VIEW_ID = 'range-view';

/** meter 类型的仪表盘 带 mask 的 view */
export const MASK_VIEW_ID = 'range-mask-view';

/**
* 仪表盘默认配置项
*/
Expand Down
89 changes: 46 additions & 43 deletions src/plots/gauge/shapes/meter-gauge.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,65 @@
import { registerShape, Types, Util } from '@antv/g2';
import { clamp } from '@antv/util';
import { GaugeCustomInfo } from '../types';

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

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

const total = this.coordinate.endAngle - this.coordinate.startAngle;
let interval = total / STEP;
let gap = 0;

/**
* stepRatio 取值范围: (0, 1]
* 1: interval : gap = stepRatio : (1 - stepRatio)
* 2: interval * STEP + stepRatio * (STEP - 1) = total
*/
if (stepRatio > 0 && stepRatio <= 1) {
interval = total / (((1 - stepRatio) / stepRatio) * (STEP - 1) + STEP);
gap = (interval * (1 - stepRatio)) / stepRatio;
const { startAngle: COORD_START_ANGLE, endAngle: COORD_END_ANGLE } = this.coordinate;
let GAP = 0;
if (STEP_RATIO > 0 && STEP_RATIO < 1) {
const TOTAL = COORD_END_ANGLE - COORD_START_ANGLE;
GAP = TOTAL / STEP / (STEP_RATIO / (1 - STEP_RATIO) + 1 - 1 / STEP);
}
const INTERVAL = (GAP / (1 - STEP_RATIO)) * STEP_RATIO;

const group = container.addGroup();
// 绘制 gap
if (gap > 0) {
const center = this.coordinate.getCenter();
const radius = this.coordinate.getRadius();
const { startAngle, endAngle } = Util.getAngle(cfg, this.coordinate);
for (let i = startAngle, j = 0; i < endAngle && j < 2 * STEP - 1; j++) {
const drawn = j % 2;
if (drawn) {
const path = Util.getSectorPath(
center.x,
center.y,
radius,
i,
Math.min(i + gap, endAngle),
radius * this.coordinate.innerRadius
);
group.addShape('path', {
name: 'meter-gauge-mask',
attrs: {
path,
fill: cfg.color,
stroke: cfg.color,
lineWidth: 0.5,
},
// mask 不需要捕捉事件
capture: false,
});
}
i += drawn ? gap : interval;
// 绘制图形的时候,留下 gap
const center = this.coordinate.getCenter();
const radius = this.coordinate.getRadius();
const { startAngle: START_ANGLE, endAngle: END_ANGLE } = Util.getAngle(cfg, this.coordinate);

for (let startAngle = START_ANGLE; startAngle < END_ANGLE; ) {
let endAngle;
const r = (startAngle - COORD_START_ANGLE) % (INTERVAL + GAP);
if (r < INTERVAL) {
endAngle = startAngle + (INTERVAL - r);
} else {
startAngle += INTERVAL + GAP - r;
endAngle = startAngle + INTERVAL;
}
const path = Util.getSectorPath(
center.x,
center.y,
radius,
startAngle,
Math.min(endAngle, END_ANGLE),
radius * this.coordinate.innerRadius
);
group.addShape('path', {
name: 'meter-gauge',
attrs: {
path,
fill: cfg.color,
stroke: cfg.color,
lineWidth: 0.5,
},
});
startAngle = endAngle + GAP;
}

return group;
Expand Down

0 comments on commit cc8fa33

Please sign in to comment.