Skip to content

Commit

Permalink
feat(column): 柱状图支持 shape (#2934)
Browse files Browse the repository at this point in the history
* feat(column): 柱状图支持 shape 配置

* feat(column): 柱状图支持 shape 添加单测和 api 文档说明

* refactor: column 类型定义添加注释
  • Loading branch information
visiky authored Oct 21, 2021
1 parent c65f6ab commit 5a22da6
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 0 deletions.
17 changes: 17 additions & 0 deletions __tests__/unit/plots/column/index-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,23 @@ describe('column', () => {
plot.destroy();
});

it('custom shape', () => {
const plot = new Column(createDiv(), {
data: salesByArea,
xField: 'area',
yField: 'sales',
color: 'red',
});

plot.render();
expect(plot.chart.geometries[0].elements[0].shape.attr('fill')).toBe('red');

plot.update({ shape: 'hollow-rect' });
expect(plot.chart.geometries[0].elements[0].shape.attr('stroke')).toBe('red');

plot.destroy();
});

it('defaultOptions 保持从 constants 中获取', () => {
expect(Column.getDefaultOptions()).toEqual(DEFAULT_OPTIONS);
});
Expand Down
6 changes: 6 additions & 0 deletions docs/api/plots/column.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ Width ratio of column [0-1].

The spacing between columns in a grouping [0-1] applies only to grouping columns.

#### shape

<description>**可选** _string_</description>

内置 shape 类型有:`hollow-rect`, `tick`; 此外,还可以搭配 [`registerShape`](https://g2.antv.vision/en/docs/api/advanced/register-shape) 进行自定义使用.

#### state

<description>**optional** _object_</description>
Expand Down
8 changes: 8 additions & 0 deletions docs/api/plots/column.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ order: 2

分组中柱子之间的间距 [0-1],仅对分组柱状图适用。

#### shape

<description>**可选** _string_</description>

内置 shape 类型有:`hollow-rect`, `tick`; 此外,还可以搭配 [`registerShape`](https://g2.antv.vision/zh/docs/api/advanced/register-shape) 进行自定义使用.

[Demo](/zh/examples/column/basic#custom-shape)

#### state

<description>**可选** _object_</description>
Expand Down
199 changes: 199 additions & 0 deletions examples/column/basic/demo/custom-shape.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { G2, Column } from '@antv/g2plot';

const LATEST_FLAG = 'LATEST_FLAG';

function getIntervalRectPath(points, isClosed = true) {
const path = [];
const firstPoint = points[0];
path.push(['M', firstPoint.x, firstPoint.y]);
for (let i = 1, len = points.length; i < len; i++) {
path.push(['L', points[i].x, points[i].y]);
}
// 对于 shape="line" path 不应该闭合,否则会造成 lineCap 绘图属性失效
if (isClosed) {
path.push(['L', firstPoint.x, firstPoint.y]); // 需要闭合
path.push(['z']);
}
return path;
}

G2.registerAnimation('label-update', (element, animateCfg, cfg) => {
const startX = element.attr('x');
const startY = element.attr('y');
// @ts-ignore
const finalX = cfg.toAttrs.x;
// @ts-ignore
const finalY = cfg.toAttrs.y;

const labelContent = element.attr('text');
// @ts-ignore
const finalContent = cfg.toAttrs.text;

const distanceX = finalX - startX;
const distanceY = finalY - startY;
const numberDiff = +finalContent - +labelContent;

element.animate((ratio) => {
const positionX = startX + distanceX * ratio;
const positionY = startY + distanceY * ratio;
const value = +labelContent + numberDiff * ratio;

return {
x: positionX,
y: positionY,
text: value.toFixed(0),
};
}, animateCfg);
});

G2.registerShape('interval', 'blink-interval', {
draw(cfg, container) {
const group = container.addGroup();
const path = this.parsePath(getIntervalRectPath(cfg.points));
const { color, style = {}, defaultStyle } = cfg;
const fillColor = color || style.fill || defaultStyle.fill;

const height = path[1][2] - path[0][2];
const width = path[3][1] - path[0][1];
const x = path[0][1];
const y = path[0][2];
group.addShape('path', {
attrs: {
...style,
path,
fill: fillColor,
x,
y,
width,
height,
},
name: 'interval',
});

const data = cfg.data;
if (data[LATEST_FLAG]) {
group.addShape('rect', {
attrs: {
x,
y,
width,
height,
fill: `l(90) 0:${fillColor} 1:rgba(255,255,255,0.23)`,
},
name: 'blink-interval',
});
}

return group;
},
});

G2.registerAnimation('appear-interval', (shape, animateCfg, cfg) => {
const growInY = G2.getAnimation('scale-in-y');
growInY(shape, animateCfg, cfg);

const blinkShape = shape.getParent().findAllByName('blink-interval')[0];
if (blinkShape) {
const { height } = blinkShape.attr();
blinkShape.attr('height', 0);
blinkShape.animate(
{
height: height,
},
{
duration: 1000,
easing: 'easeQuadOut',
repeat: true,
}
);
}
});

G2.registerAnimation('blink-interval', (element, animateCfg, cfg) => {
const container = element.getParent();

const shape = container.findAllByName('interval')[0];
const blinkShape = container.findAllByName('blink-interval')[0];

if (shape && cfg.toAttrs.path) {
shape.animate(cfg.toAttrs, animateCfg);
}

if (blinkShape) {
blinkShape.stopAnimate(true);
blinkShape.attr({ x: cfg.toAttrs.x, y: cfg.toAttrs.y });
blinkShape.attr({ height: 0, width: cfg.toAttrs.width });
blinkShape.animate(
{
height: cfg.toAttrs.height,
},
{
duration: 1000,
easing: 'easeQuadOut',
delay: 50,
repeat: true,
}
);
}
});

fetch('https://gw.alipayobjects.com/os/antfincdn/xXg6cUV0lV/column.json')
.then((data) => data.json())
.then((data) => {
const plot = new Column('container', {
data: data.map((d, idx) => ({ ...d, [LATEST_FLAG]: idx === data.length - 1 })),
xField: 'month',
yField: 'value',
appendPadding: [10],
label: {
// 可手动配置 label 数据标签位置
position: 'top', // 'top', 'bottom', 'middle',
offset: 4,
animate: {
update: {
animation: 'label-update',
duration: 300,
easing: 'easeLinear',
},
},
},
xAxis: {
label: {
autoHide: true,
autoRotate: false,
},
},
meta: {
month: {
alias: '月份',
},
value: {
alias: '销售额',
nice: true,
},
},
shape: 'blink-interval',
animation: {
appear: {
animation: 'appear-interval',
},
update: {
animation: 'blink-interval',
},
},
});

plot.render();
let timer = 0;
const interval = setInterval(() => {
const chartData = plot.chart.getData();
plot.chart.changeData(
chartData.map((d) => ({ ...d, value: d[LATEST_FLAG] ? d.value + ((Math.random() * 250) | 0) : d.value }))
);

timer++;
if (timer > 500) {
clearInterval(interval);
}
}, 2000);
});
9 changes: 9 additions & 0 deletions examples/column/basic/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@
"en": "Basic column plot with region annotation"
},
"screenshot": "https://gw.alipayobjects.com/zos/antfincdn/ceFSs9FuNn/2922d5e4-df5f-4512-8f8f-6f2ec258c7b8.png"
},
{
"filename": "custom-shape.ts",
"title": {
"zh": "自定义柱状图图形元素展示",
"en": "Custom column shape"
},
"new": true,
"screenshot": ""
}
]
}
2 changes: 2 additions & 0 deletions src/plots/column/adaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function geometry(params: Params<ColumnOptions>): Params<ColumnOptions> {
seriesField,
groupField,
tooltip,
shape,
} = options;

const percentData =
Expand Down Expand Up @@ -110,6 +111,7 @@ function geometry(params: Params<ColumnOptions>): Params<ColumnOptions> {
widthRatio: columnWidthRatio,
tooltip: tooltipOptions,
interval: {
shape,
style: columnStyle,
color,
},
Expand Down
5 changes: 5 additions & 0 deletions src/plots/column/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ export interface ColumnOptions
/** 分组字段,优先级高于 seriesField , isGroup: true 时会根据 groupField 进行分组。*/
readonly groupField?: string;

// 自定义相关
/** 自定义柱状图 interval 图形元素展示形状 */
readonly shape?: string;

// 图表交互
/** 开启下钻交互,以及进行下钻交互的配置 */
readonly brush?: BrushCfg;
}

0 comments on commit 5a22da6

Please sign in to comment.