Skip to content

Commit

Permalink
feat(v2/column): 添加基础柱形图 (#1316)
Browse files Browse the repository at this point in the history
* feat(v2/column): add column plot

* feat(v2/column): export ColumnOptions & add findGeometry helper

* feat(v2/column): add label formatter unit test

* feat(v2/column): simplify findGeometry signature

* feat(v2/column): changes with cr
  • Loading branch information
lessmost authored Jul 22, 2020
1 parent 0b481d5 commit 62683d5
Show file tree
Hide file tree
Showing 13 changed files with 507 additions and 7 deletions.
8 changes: 8 additions & 0 deletions __tests__/data/sales.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const salesByArea = [
{ area: '东北', sales: 2681567.469000001 },
{ area: '中南', sales: 4137415.0929999948 },
{ area: '华东', sales: 4684506.442 },
{ area: '华北', sales: 2447301.017000004 },
{ area: '西北', sales: 815039.5959999998 },
{ area: '西南', sales: 1303124.508000002 },
];
75 changes: 75 additions & 0 deletions __tests__/unit/plots/column/axis-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Column } from '../../../../src';
import { salesByArea } from '../../../data/sales';
import { createDiv } from '../../../utils/dom';

describe('column axis', () => {
it('meta', () => {
const formatter = (v) => `${Math.floor(v / 10000)}万`;
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
meta: {
sales: {
nice: true,
formatter,
},
},
});

column.render();

const geometry = column.chart.geometries[0];
// @ts-ignore
expect(geometry.scales.sales.nice).toBe(true);
expect(geometry.scales.sales.formatter).toBe(formatter);
});

it('xAxis', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
xAxis: {
label: {
rotate: -Math.PI / 2,
},
},
});

column.render();
const axisOptions = column.chart.getOptions().axes;

// @ts-ignore
expect(axisOptions.area.label.rotate).toBe(-Math.PI / 2);
});

it('yAxis', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
yAxis: {
minLimit: 10000,
nice: true,
},
});

column.render();

const geometry = column.chart.geometries[0];
const axisOptions = column.chart.getOptions().axes;

// @ts-ignore
expect(axisOptions.sales.minLimit).toBe(10000);
expect(geometry.scales.sales.minLimit).toBe(10000);
// @ts-ignore
expect(geometry.scales.sales.nice).toBe(true);
});
});
74 changes: 74 additions & 0 deletions __tests__/unit/plots/column/index-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Column } from '../../../../src';
import { salesByArea } from '../../../data/sales';
import { createDiv } from '../../../utils/dom';

describe('column', () => {
it('x*y', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
});

column.render();

const geometry = column.chart.geometries[0];
const positionFields = geometry.getAttribute('position').getFields();

// 类型
expect(geometry.type).toBe('interval');
// 图形元素个数
expect(column.chart.geometries[0].elements.length).toBe(salesByArea.length);
// x & y
expect(positionFields).toHaveLength(2);
expect(positionFields[0]).toBe('area');
expect(positionFields[1]).toBe('sales');
});

it('x*y*color', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
colorField: 'area',
});

column.render();

const geometry = column.chart.geometries[0];
const colorFields = geometry.getAttribute('color').getFields();

expect(colorFields).toHaveLength(1);
expect(colorFields[0]).toBe('area');
});

it('x*y*color with color', () => {
const palette = ['red', 'yellow', 'green'];
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
colorField: 'area',
color: palette,
});

column.render();

const geometry = column.chart.geometries[0];
const colorAttribute = geometry.getAttribute('color');
const colorFields = colorAttribute.getFields();

expect(colorFields).toHaveLength(1);
expect(colorFields[0]).toBe('area');
geometry.elements.forEach((element, index) => {
const color = element.getModel().color;
expect(color).toBe(palette[index % palette.length]);
});
});
});
90 changes: 90 additions & 0 deletions __tests__/unit/plots/column/label-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Column } from '../../../../src';
import { salesByArea } from '../../../data/sales';
import { createDiv } from '../../../utils/dom';

describe('column label', () => {
it('position: top', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
meta: {
sales: {
nice: true,
formatter: (v) => `${Math.floor(v / 10000)}万`,
},
},
label: {
position: 'top',
},
});

column.render();

const geometry = column.chart.geometries[0];
const labelGroups = geometry.labelsContainer.getChildren();

// @ts-ignore
expect(geometry.labelOption.cfg).toEqual({
position: 'top',
});
expect(labelGroups).toHaveLength(salesByArea.length);
labelGroups.forEach((label, index) => {
expect(label.get('children')[0].attr('text')).toBe(`${Math.floor(salesByArea[index].sales / 10000)}万`);
});
});

it('label position middle', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
meta: {
sales: {
nice: true,
formatter: (v) => `${Math.floor(v / 10000)}万`,
},
},
label: {
position: 'middle',
},
});

column.render();

const geometry = column.chart.geometries[0];

// @ts-ignore
expect(geometry.labelOption.cfg).toEqual({ position: 'middle' });
});

it('label position bottom', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
meta: {
sales: {
nice: true,
formatter: (v) => `${Math.floor(v / 10000)}万`,
},
},
label: {
position: 'bottom',
},
});

column.render();

const geometry = column.chart.geometries[0];

// @ts-ignore
expect(geometry.labelOption.cfg).toEqual({ position: 'bottom' });
});
});
61 changes: 61 additions & 0 deletions __tests__/unit/plots/column/style-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Column } from '../../../../src';
import { salesByArea } from '../../../data/sales';
import { createDiv } from '../../../utils/dom';

describe('column style', () => {
it('style config', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
meta: {
sales: {
nice: true,
formatter: (v) => `${Math.floor(v / 10000)}万`,
},
},
columnStyle: {
stroke: 'black',
lineWidth: 2,
},
});

column.render();

const geometry = column.chart.geometries[0];
const elements = geometry.elements;
expect(elements[0].shape.attr('stroke')).toBe('black');
expect(elements[0].shape.attr('lineWidth')).toBe(2);
});

it('style callback', () => {
const column = new Column(createDiv(), {
width: 400,
height: 300,
data: salesByArea,
xField: 'area',
yField: 'sales',
meta: {
sales: {
nice: true,
formatter: (v) => `${Math.floor(v / 10000)}万`,
},
},
columnStyle: (x, y) => {
return {
stroke: 'black',
lineWidth: 2,
};
},
});

column.render();

const geometry = column.chart.geometries[0];
const elements = geometry.elements;
expect(elements[0].shape.attr('stroke')).toBe('black');
expect(elements[0].shape.attr('lineWidth')).toBe(2);
});
});
10 changes: 10 additions & 0 deletions src/common/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Chart, Geometry } from '@antv/g2';

/**
* 在 Chart 中查找第一个指定 type 类型的 geometry
* @param chart
* @param type
*/
export function findGeometry(chart: Chart, type: string): Geometry {
return chart.geometries.find((g: Geometry) => g.type === type);
}
13 changes: 13 additions & 0 deletions src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* 需要从轴配置中提取出来作为 meta 的属性 key 列表
*/
export const AXIS_META_CONFIG_KEYS = [
'tickCount',
'tickInterval',
'min',
'max',
'nice',
'minLimit',
'maxLimit',
'tickMethod',
];
4 changes: 2 additions & 2 deletions src/core/plot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { ChartOptions, Data } from '../types';
*/
export abstract class Plot<O extends ChartOptions> {
/** plot 类型名称 */
public abstract type: string = 'base';
public abstract readonly type: string = 'base';
/** plot 的 schema 配置 */
public options: O;
/** plot 绘制的 dom */
public container: HTMLElement;
public readonly container: HTMLElement;
/** G2 chart 实例 */
public chart: Chart;
/** resizer unbind */
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export * from './types';
// 折线图及类型定义
export { Line, LineOptions } from './plots/line';

// 柱形图及类型定义
export { Column, ColumnOptions } from './plots/column';

// 饼图及类型定义
export { Pie, PieOptions } from './plots/pie';

Expand Down
Loading

0 comments on commit 62683d5

Please sign in to comment.