Skip to content

Commit

Permalink
Merge pull request #389 from antvis/feat-waterfall
Browse files Browse the repository at this point in the history
 feat(waterfall): 添加瀑布图
  • Loading branch information
paleface001 authored Dec 30, 2019
2 parents 8f6f33a + 38483fb commit 4c6d703
Show file tree
Hide file tree
Showing 16 changed files with 780 additions and 0 deletions.
170 changes: 170 additions & 0 deletions __tests__/unit/waterfall-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Waterfall } from '../../src';
import { Shape } from '@antv/g';
import * as _ from '@antv/util';

describe('waterfall plot', () => {
const data = [
{ type: '日用品', money: 300 },
{ type: '伙食费', money: 900 },
{ type: '交通费', money: 200 },
{ type: '水电费', money: 300 },
{ type: '房租', money: 1200 },
{ type: '商场消费', money: 1000 },
{ type: '应酬交际', money: -2000 },
];

const plotOptions = {
title: {
visible: true,
text: '每月收支情况(瀑布图)',
},
forceFit: true,
data,
padding: 'auto',
data,
xField: 'type',
yField: 'money',
meta: {
type: {
alias: '类别',
},
money: {
alias: '金额',
},
},
};

const canvasDiv = document.createElement('div');
canvasDiv.style.width = '600px';
canvasDiv.style.height = '600px';
canvasDiv.style.left = '30px';
canvasDiv.style.top = '30px';
canvasDiv.id = 'canvas1';
document.body.appendChild(canvasDiv);

const waterfallPlot = new Waterfall(canvasDiv, plotOptions);
const waterfallLayer = waterfallPlot.getLayer();

it('normal', () => {
waterfallPlot.render();
const shapes = waterfallLayer.view.get('elements')[0].getShapes();
expect(shapes.length).toBe(data.length * 2 + 1);
const lines = shapes.filter((s) => s.name === 'leader-line');
expect(lines.length).toBe(data.length);
});

it('custom color, string', () => {
waterfallPlot.updateConfig({
color: 'rgba(0, 255, 255, 0.2)',
});
waterfallPlot.render();
const shapes = waterfallLayer.view
.get('elements')[0]
.getShapes()
.filter((s) => s.name === 'interval');
expect(_.every(shapes, (s: Shape) => s.attr('fill') === 'rgba(0, 255, 255, 0.2)')).toBe(true);
});

it('custom color, object', () => {
waterfallPlot.updateConfig({
color: {
rising: 'red',
falling: 'green',
total: '#ddd',
},
});
waterfallPlot.render();
const shapes = waterfallLayer.view
.get('elements')[0]
.getShapes()
.filter((s) => s.name === 'interval');
expect(shapes[0].attr('fill')).toBe('red');
expect(shapes[6].attr('fill')).toBe('green');
expect(_.last(shapes).attr('fill')).toBe('#ddd');
});

it('use callback to custom color', () => {
waterfallPlot.updateConfig({
color: (type, value, values, index) => {
if (index === data.length) {
return '#ddd';
} else if (value > 0) {
return 'rgba(255, 0, 0, 0.45)';
}
return 'rgba(255, 255, 0, 0.45)';
},
});
waterfallPlot.render();
const shapes = waterfallLayer.view
.get('elements')[0]
.getShapes()
.filter((s) => s.name === 'interval');
expect(shapes[0].attr('fill')).toBe('rgba(255, 0, 0, 0.45)');
expect(shapes[6].attr('fill')).toBe('rgba(255, 255, 0, 0.45)');
expect(_.last(shapes).attr('fill')).toBe('#ddd');
});

it('not show total', () => {
waterfallPlot.updateConfig({
showTotal: {
visible: false,
},
});
waterfallPlot.render();
const shapes = waterfallLayer.view.get('elements')[0].getShapes();
expect(shapes.length).toBe(data.length * 2 - 1);
const lines = shapes.filter((s) => s.name === 'leader-line');
expect(lines.length).toBe(data.length - 1);
});

it('diff-label', () => {
waterfallPlot.updateConfig({
diffLabel: {
visible: true,
style: {
fill: 'red',
},
formatter: (text, item, index) => {
if (text.startsWith('+')) {
return `涨 ${text.substr(1)}`;
} else if (text.startsWith('-')) {
return `跌 ${text.substr(1)}`;
}
return text;
},
},
});
waterfallPlot.render();
const shapes = waterfallLayer.view.get('elements')[0].getShapes();
// @ts-ignore
const diffLabel = waterfallLayer.diffLabel;
const labelShapes = diffLabel.container.get('children')[0].get('children');
const intervals = shapes.filter((s) => s.name === 'interval');
/** auto hide label that is overflowed */
expect(labelShapes.length).toBe(intervals.length);
expect(
_.every(
intervals,
(shape: Shape, idx) => labelShapes[idx].attr('y') === (shape.getBBox().minY + shape.getBBox().maxY) / 2
)
).toBe(true);
expect(labelShapes[0].attr('text')).toBe('涨 300');
expect(labelShapes[0].attr('fill')).toBe('red');
});

it('hidden diff-label', () => {
waterfallPlot.updateConfig({
diffLabel: {
visible: false,
},
});
waterfallPlot.render();
// @ts-ignore
const diffLabel = waterfallLayer.diffLabel;
expect(diffLabel).toBe(null);
});

afterAll(() => {
// waterfallPlot.destroy();
});
});
13 changes: 13 additions & 0 deletions examples/column/waterfall/API.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: API
---

API.

- Modern browsers and Internet Explorer 9+ (with [polyfills](https:// ant.design/docs/react/getting-started#Compatibility))
- Server-side Rendering
- [Electron](http:// electron.atom.io/)

| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http:// godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Electron |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| IE9, IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
13 changes: 13 additions & 0 deletions examples/column/waterfall/API.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: API
---

暂无。

- Modern browsers and Internet Explorer 9+ (with [polyfills](https:// ant.design/docs/react/getting-started#Compatibility))
- Server-side Rendering
- [Electron](http:// electron.atom.io/)

| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http:// godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Electron |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| IE9, IE10, IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
34 changes: 34 additions & 0 deletions examples/column/waterfall/demo/basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Waterfall } from '@antv/g2plot';

const data = [
{ type: '日用品', money: 120 },
{ type: '伙食费', money: 900 },
{ type: '交通费', money: 200 },
{ type: '水电费', money: 300 },
{ type: '房租', money: 1200 },
{ type: '商场消费', money: 1000 },
{ type: '应酬红包', money: -2000 },
];

const waterfallPlot = new Waterfall(document.getElementById('container'), {
title: {
visible: true,
text: '每月收支情况(瀑布图)',
},
forceFit: true,
data,
padding: 'auto',
data,
xField: 'type',
yField: 'money',
meta: {
type: {
alias: '类别',
},
money: {
alias: '金额',
},
},
});

waterfallPlot.render();
13 changes: 13 additions & 0 deletions examples/column/waterfall/demo/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": {
"zh": "中文分类",
"en": "Category"
},
"demos": [
{
"filename": "basic.js",
"title": "基础瀑布图",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_d314dd/afts/img/A*gTX1T6UddcYAAAAAAAAAAABkARQnAQ"
}
]
}
5 changes: 5 additions & 0 deletions examples/column/waterfall/design.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: 设计规范
---

设计规范
5 changes: 5 additions & 0 deletions examples/column/waterfall/design.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: 设计规范
---

设计规范
6 changes: 6 additions & 0 deletions examples/column/waterfall/index.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Waterfall Chart
order: 5
---

Description about this component.
5 changes: 5 additions & 0 deletions examples/column/waterfall/index.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: 瀑布图
order: 5
---

1 change: 1 addition & 0 deletions src/plots/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export { default as Area, AreaConfig } from './area';
export { default as StackArea, StackAreaConfig } from './stack-area';
export { default as PercentageStackArea, PercentageStackAreaConfig } from './percentage-stack-area';
export { default as Heatmap, HeatmapConfig } from './heatmap';
export { default as Waterfall, WaterfallConfig } from './waterfall';
108 changes: 108 additions & 0 deletions src/plots/waterfall/component/label/diff-label.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Group, BBox } from '@antv/g';
import { View } from '@antv/g2';
import * as _ from '@antv/util';
import { VALUE_FIELD, IS_TOTAL } from '../../layer';

export interface DiffLabelcfg {
view: View;
fields: string[];
formatter: (text: string, item: object, idx: number) => string;
style?: {
fill?: string;
stroke?: string;
strokeOpacity?: number;
[k: string]: any;
};
}

function getDefaultCfg() {
return {
fill: '#fff',
fontSize: 12,
lineHeight: 12,
stroke: 'rgba(0, 0, 0, 0.45)',
};
}

export default class DiffLabel {
private view: View;
private fields: string[];
private container: Group;
private formatter: (text: string, item: object, idx: number) => string;
private textAttrs: object = {};

constructor(cfg: DiffLabelcfg) {
this.view = cfg.view;
this.fields = cfg.fields;
this.formatter = cfg.formatter;
this.textAttrs = _.mix(getDefaultCfg(), cfg.style);

this._init();
}

/** 绘制辅助labels */
public draw() {
if (!this.view || this.view.destroyed) {
return;
}
const data = _.clone(this.view.get('data'));
this.container = this.view.get('frontgroundGroup').addGroup();
const shapes = this.view
.get('elements')[0]
.getShapes()
.filter((s) => s.name === 'interval');
const labelsGroup = new Group();

_.each(shapes, (shape, idx) => {
if (!shape.get('origin')) return;
const _origin = shape.get('origin')._origin;
const shapeBox: BBox = shape.getBBox();
const values = _origin[VALUE_FIELD];
let diff = values;
if (_.isArray(values)) {
diff = values[1] - values[0];
}
diff = diff > 0 ? `+${diff}` : diff;
/** is total, total do not need `+` sign */
if (_origin[IS_TOTAL]) {
diff = values[0] - values[1];
}
let formattedText = diff;
if (this.formatter) {
const color = shapes[idx].attr('fill');
formattedText = this.formatter(`${diff}`, { _origin: data[idx], color }, idx);
}
const text = labelsGroup.addShape('text', {
attrs: {
text: formattedText,
textBaseline: 'middle',
textAlign: 'center',
x: (shapeBox.minX + shapeBox.maxX) / 2,
y: (shapeBox.minY + shapeBox.maxY) / 2,
...this.textAttrs,
},
});
if (text.getBBox().height > shapeBox.height) {
text.set('visible', false);
}
});
this.container.add(labelsGroup);
this.view.get('canvas').draw();
}

public clear() {
if (this.container) {
this.container.clear();
}
}

private _init() {
this.view.on('beforerender', () => {
this.clear();
});

this.view.on('afterrender', () => {
this.draw();
});
}
}
Loading

0 comments on commit 4c6d703

Please # to comment.