├── .babelrc
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── README.md
├── demo
├── Line
│ └── index.jsx
├── Pie
│ └── index.jsx
├── index.html
├── index.js
└── webpack.config.js
├── jest.config.js
├── package.json
├── src
├── common
│ └── util.js
├── component
│ ├── axis
│ │ └── index.js
│ ├── chart
│ │ └── index.js
│ ├── coord
│ │ └── index.js
│ ├── geom
│ │ ├── area.js
│ │ ├── interval.js
│ │ ├── line.js
│ │ └── point.js
│ ├── guide
│ │ └── index.js
│ ├── legend
│ │ └── index.js
│ ├── scale
│ │ └── index.js
│ └── tooltip
│ │ └── index.js
├── f2.js
├── index.js
└── util.js
├── test
├── common
│ └── util.test.js
└── component
│ ├── area.test.js
│ ├── axis.test.js
│ ├── chart.test.js
│ ├── coord.test.js
│ ├── guide.test.js
│ ├── interval.test.js
│ ├── legend.test.js
│ ├── line.test.js
│ ├── point.test.js
│ ├── scale.test.js
│ └── tooltip.test.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "react",
5 | "stage-0"
6 | ],
7 | "plugins": [
8 | "transform-export-extensions"
9 | ]
10 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib/
2 | node_modules/
3 | coverage/
4 | demo/
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true,
6 | "jest": true,
7 | },
8 | "extends": ["eslint-config-airbnb"],
9 | "plugins": [
10 | 'react',
11 | 'babel',
12 | ],
13 | "rules": {
14 | "no-plusplus": 0,
15 | "max-len": [1, 130],
16 | "no-console": 0,
17 | "linebreak-style": [2, "unix"],
18 | "quotes": [2, "single"],
19 | "semi": [2, "always"],
20 | "arrow-body-style": 0,
21 | "react/prop-types": 0,
22 | "guard-for-in": 0,
23 | "no-restricted-syntax": 0,
24 | "react/no-string-refs": 0,
25 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
26 | "import/newline-after-import": 0,
27 | "import/no-unresolved": [2, { ignore: ['^react$'] }],
28 | "import/extensions": 0,
29 | "import/no-extraneous-dependencies": 0
30 | }
31 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | node_modules
4 | umd
5 | lib
6 | es6
7 | coverage
8 | npm-debug.log
9 | *.orig
10 | .vscode/*
11 | dist/
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .idea
4 | coverage
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ant-design-mobile-chart
2 |
3 | ## 说明
4 |
5 | `ant-design-mobile-chart` 是为移动端开发的 react 图表组件库,图形语法底层基于[F2](https://antv.alipay.com/zh-cn/f2/3.x/index.html) 实现图形语法。 组件库具有轻量、灵活、易用的特点
6 |
7 | ## 安装
8 |
9 | ```bash
10 | $ npm install ant-design-mobile-chart --save
11 | ```
12 |
13 | ## 使用
14 |
15 | ```jsx
16 | import React, { Component } from 'react';
17 | import { Chart, Line, Scale, Guide } from 'ant-design-mobile-chart';
18 |
19 | const data = [
20 | {
21 | "reportDateTimestamp": 1529856000000,
22 | "codeType": "INDEX_CODE",
23 | "rate": 0
24 | },
25 | {
26 | "reportDateTimestamp": 1529942400000,
27 | "codeType": "INDEX_CODE",
28 | "rate": 0.0082
29 | },
30 | {
31 | "reportDateTimestamp": 1530028800000,
32 | "codeType": "INDEX_CODE",
33 | "rate": 0.0284
34 | },
35 | {
36 | "reportDateTimestamp": 1530115200000,
37 | "codeType": "INDEX_CODE",
38 | "rate": -0.0385
39 | },
40 | {
41 | "reportDateTimestamp": 1530201600000,
42 | "codeType": "INDEX_CODE",
43 | "rate": -0.0139
44 | },
45 | {
46 | "reportDateTimestamp": 1530460800000,
47 | "codeType": "INDEX_CODE",
48 | "rate": -0.0428
49 | },
50 | {
51 | "reportDateTimestamp": 1530547200000,
52 | "codeType": "INDEX_CODE",
53 | "rate": 0.0425
54 | },
55 | {
56 | "reportDateTimestamp": 1529856000000,
57 | "codeType": "PRODUCT_ID",
58 | "rate": 0
59 | },
60 | {
61 | "reportDateTimestamp": 1529942400000,
62 | "codeType": "PRODUCT_ID",
63 | "rate": -0.0075
64 | },
65 | {
66 | "reportDateTimestamp": 1530028800000,
67 | "codeType": "PRODUCT_ID",
68 | "rate": -0.0264
69 | },
70 | {
71 | "reportDateTimestamp": 1530115200000,
72 | "codeType": "PRODUCT_ID",
73 | "rate": -0.0355
74 | },
75 | {
76 | "reportDateTimestamp": 1530201600000,
77 | "codeType": "PRODUCT_ID",
78 | "rate": -0.0113
79 | },
80 | {
81 | "reportDateTimestamp": 1530460800000,
82 | "codeType": "PRODUCT_ID",
83 | "rate": -0.0383
84 | },
85 | {
86 | "reportDateTimestamp": 1530547200000,
87 | "codeType": "PRODUCT_ID",
88 | "rate": -0.0377
89 | }
90 | ]
91 |
92 | class LineDemo extends Component {
93 | render() {
94 | return (
95 |
96 |
97 |
98 |
99 | `${(rate*100).toFixed(2)}%`} />
100 |
101 |
102 |
103 | );
104 | }
105 | }
106 |
107 | export default LineDemo;
108 |
109 | ```
110 |
111 | 
112 |
--------------------------------------------------------------------------------
/demo/Line/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Chart, Line, Scale, Guide } from 'ant-design-mobile-chart';
3 |
4 |
5 | const data = [
6 | {
7 | "reportDateTimestamp": 1529856000000,
8 | "codeType": "INDEX_CODE",
9 | "rate": 0
10 | },
11 | {
12 | "reportDateTimestamp": 1529942400000,
13 | "codeType": "INDEX_CODE",
14 | "rate": 0.0082
15 | },
16 | {
17 | "reportDateTimestamp": 1530028800000,
18 | "codeType": "INDEX_CODE",
19 | "rate": 0.0284
20 | },
21 | {
22 | "reportDateTimestamp": 1530115200000,
23 | "codeType": "INDEX_CODE",
24 | "rate": -0.0385
25 | },
26 | {
27 | "reportDateTimestamp": 1530201600000,
28 | "codeType": "INDEX_CODE",
29 | "rate": -0.0139
30 | },
31 | {
32 | "reportDateTimestamp": 1530460800000,
33 | "codeType": "INDEX_CODE",
34 | "rate": -0.0428
35 | },
36 | {
37 | "reportDateTimestamp": 1530547200000,
38 | "codeType": "INDEX_CODE",
39 | "rate": 0.0425
40 | },
41 | {
42 | "reportDateTimestamp": 1529856000000,
43 | "codeType": "PRODUCT_ID",
44 | "rate": 0
45 | },
46 | {
47 | "reportDateTimestamp": 1529942400000,
48 | "codeType": "PRODUCT_ID",
49 | "rate": -0.0075
50 | },
51 | {
52 | "reportDateTimestamp": 1530028800000,
53 | "codeType": "PRODUCT_ID",
54 | "rate": -0.0264
55 | },
56 | {
57 | "reportDateTimestamp": 1530115200000,
58 | "codeType": "PRODUCT_ID",
59 | "rate": -0.0355
60 | },
61 | {
62 | "reportDateTimestamp": 1530201600000,
63 | "codeType": "PRODUCT_ID",
64 | "rate": -0.0113
65 | },
66 | {
67 | "reportDateTimestamp": 1530460800000,
68 | "codeType": "PRODUCT_ID",
69 | "rate": -0.0383
70 | },
71 | {
72 | "reportDateTimestamp": 1530547200000,
73 | "codeType": "PRODUCT_ID",
74 | "rate": -0.0377
75 | }
76 | ]
77 |
78 |
79 | class LineDemo extends Component {
80 | render() {
81 | return (
82 |
83 |
84 |
85 |
86 | `${(rate*100).toFixed(2)}%`} />
87 |
88 |
89 |
90 | );
91 | }
92 | }
93 |
94 | export default LineDemo;
95 |
--------------------------------------------------------------------------------
/demo/Pie/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Chart, Legend, Coord, Interval, Guide, Tooltip, Axis, F2 } from 'ant-design-mobile-chart';
3 | const { Group, Shape } = F2.G;
4 |
5 | const data = [
6 | {
7 | "name": "余额",
8 | "color": "64C4EF",
9 | "value": "10000.00",
10 | "proportion": 0.07,
11 | "spm": "c12899.d23652",
12 | "a": "1"
13 | },
14 | {
15 | "name": "余额宝",
16 | "color": "F5865C",
17 | "value": "20000.00",
18 | "proportion": 0.13,
19 | "spm": "c12899.d23653",
20 | "a": "1"
21 | },
22 | {
23 | "name": "定期",
24 | "color": "3DA4EE",
25 | "value": "30000.00",
26 | "proportion": 0.20,
27 | "spm": "c12899.d23656",
28 | "a": "1"
29 | },
30 | {
31 | "name": "基金",
32 | "color": "6088EC",
33 | "value": "40000.00",
34 | "proportion": 0.27,
35 | "spm": "c12899.d23655",
36 | "a": "1"
37 | },
38 | {
39 | "name": "黄金",
40 | "color": "F8CC49",
41 | "value": "50000.00",
42 | "proportion": 0.33,
43 | "spm": "c12899.d23654",
44 | "a": "1"
45 | }
46 | ];
47 | const OFFSET = 20; // 偏移量
48 |
49 | class PieDemo extends Component {
50 | _getEndPoint(center, angle, r) {
51 | return {
52 | x: center.x + (r * Math.cos(angle)),
53 | y: center.y + (r * Math.sin(angle)),
54 | };
55 | }
56 |
57 | _addPieLabel(chart) {
58 | const coord = chart.get('coord');
59 | const geom = chart.get('geoms')[0];
60 | const shapes = geom.get('shapes');
61 | const { center } = coord; // 极坐标圆心坐标
62 | const r = coord.circleRadius; // 极坐标半径
63 | const canvas = chart.get('canvas'); // 获取 canvas 对象
64 | const labelGroup = canvas.addGroup(); // 用于存储文本以及文本连接线
65 |
66 | shapes.forEach((shape) => {
67 | const shapeAttrs = shape.attr();
68 | const origin = shape.get('origin');
69 | const { startAngle, endAngle } = shapeAttrs;
70 | const middleAngle = (startAngle + endAngle) / 2;
71 | const routerPoint = this._getEndPoint(center, middleAngle, r + OFFSET);
72 | const textName = new Shape.Text({
73 | attrs: {
74 | x: routerPoint.x,
75 | y: routerPoint.y,
76 | fontSize: 12,
77 | fill: origin.color,
78 | text: origin._origin.name,
79 | textBaseline: 'middle',
80 | lineHeight: 12,
81 | },
82 | origin: origin._origin, // 存储原始数据
83 | });
84 | labelGroup.add(textName);
85 | });
86 | canvas.draw();
87 | }
88 |
89 | onRendered = (chart) => {
90 | this._addPieLabel(chart);
91 | }
92 |
93 | render() {
94 | const html = '150000.00
';
95 | const marker = {
96 | symbol: 'square',
97 | radius: 4,
98 | };
99 | const legendMapData = {};
100 | data.forEach(item => {
101 | legendMapData[item.name] = item.value;
102 | });
103 | const animate = {
104 | appear: {
105 | animation: 'groupScaleInXY',
106 | easing: 'elasticOut',
107 | delay: 300,
108 | duration: 300,
109 | },
110 | };
111 | const itemFormatter = val => { return val + ' ' + legendMapData[val]; };
112 | return
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
;
122 | }
123 | }
124 |
125 | export default PieDemo;
126 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | antd-design-mobile-chart
8 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import LineDemo from './Line';
4 | import PieDemo from './Pie';
5 |
6 | class Demo extends Component {
7 | render() {
8 | return ();
12 | }
13 | }
14 |
15 | ReactDOM.render(, document.getElementById('mountNode'));
16 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './demo/index.js',
5 | devServer: {
6 | contentBase: './demo',
7 | stats: 'minimal',
8 | port: 9001,
9 | disableHostCheck: true,
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.(js|jsx)$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: 'babel-loader',
18 | options: {
19 | plugins: [
20 | // [
21 | // "import",
22 | // {
23 | // "libraryName": "ant-design-mobile-chart",
24 | // "libraryDirectory": "src"
25 | // }
26 | // ]
27 | ]
28 | }
29 | }
30 | },
31 | { test: /\.css$/, use: ['style-loader', 'css-loader'] },
32 | ]
33 | },
34 | plugins: [],
35 | externals: {
36 | // react: 'React',
37 | // 'react-dom': 'ReactDOM',
38 | },
39 | resolve: {
40 | extensions: ['.js', '.jsx'],
41 | alias: {
42 | 'ant-design-mobile-chart': path.join(__dirname, '..'),
43 | },
44 | },
45 | output: {
46 | filename: 'bundle.js',
47 | path: path.resolve(__dirname, 'dist')
48 | },
49 | mode: 'development',
50 | };
51 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | setupFiles: [
3 | 'jest-canvas-mock',
4 | ],
5 | collectCoverageFrom: ['/src/component/**/*.{js,jsx}', '/src/common/*.{js,jsx}'],
6 | };
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ant-design-mobile-chart",
3 | "version": "1.2.2",
4 | "description": "react移动端图表库",
5 | "main": "./lib/index.js",
6 | "scripts": {
7 | "prepublish": "npm run compile",
8 | "compile": "rm -rf lib && babel src --out-dir lib && npm run umd",
9 | "lint": "eslint ./",
10 | "test": "jest test",
11 | "demo": "webpack-dev-server --open --mode development --config demo/webpack.config.js",
12 | "start": "npm run demo",
13 | "build": "rm -rf lib && babel src --out-dir lib && npm run umd",
14 | "umd": "webpack"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/ant-design/ant-design-mobile-chart.git"
19 | },
20 | "keywords": [
21 | "chart",
22 | "react",
23 | "f2"
24 | ],
25 | "author": "cycgit",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/ant-design/ant-design-mobile-chart/issues"
29 | },
30 | "homepage": "https://github.com/ant-design/ant-design-mobile-chart#readme",
31 | "dependencies": {
32 | "@antv/f2": "~3.5.0"
33 | },
34 | "devDependencies": {
35 | "babel-cli": "^6.26.0",
36 | "babel-core": "^6.26.0",
37 | "babel-eslint": "^7.2.3",
38 | "babel-loader": "^7.1.4",
39 | "babel-plugin-import": "^1.7.0",
40 | "babel-plugin-transform-export-extensions": "^6.22.0",
41 | "babel-preset-env": "^1.6.1",
42 | "babel-preset-react": "^6.24.1",
43 | "babel-preset-stage-0": "^6.24.1",
44 | "css-loader": "^0.28.11",
45 | "enzyme": "^3.3.0",
46 | "enzyme-adapter-react-16": "^1.1.1",
47 | "eslint": "^4.19.1",
48 | "eslint-config-airbnb": "^16.1.0",
49 | "eslint-plugin-babel": "^5.1.0",
50 | "eslint-plugin-import": "^2.10.0",
51 | "eslint-plugin-jsx-a11y": "^5.1.1",
52 | "eslint-plugin-react": "^7.7.0",
53 | "extract-text-webpack-plugin": "^4.0.0-beta.0",
54 | "jest": "^23.1.0",
55 | "jest-canvas-mock": "^1.0.2",
56 | "react": "^16.4.0",
57 | "react-dom": "^16.3.2",
58 | "react-hot-loader": "^4.1.2",
59 | "style-loader": "^0.21.0",
60 | "webpack": "^4.6.0",
61 | "webpack-cli": "^3.1.1",
62 | "webpack-dev-server": "^3.1.3"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/common/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 判断是否方法
3 | * @param {Array} obj
4 | * @return {Boolean} bool
5 | */
6 | function isFunction(obj) {
7 | return Object.prototype.toString.call(obj) === '[object Function]';
8 | }
9 |
10 | /**
11 | * 判断是否数组对象
12 | * @param {Array} obj
13 | * @return {Boolean} bool
14 | */
15 | function isArray(obj) {
16 | return Array.isArray(obj);
17 | }
18 |
19 | /**
20 | * 暂时先放这里
21 | * 不考虑用core-js等类库,原因:
22 | * 1. 这种类库会增加Array.prototype.includes,引用后如果哪天去掉了,但是如果被外部直接使用时就会有坑
23 | * @param {Array} arr 数组对象
24 | * @param {Object} item 数组元素
25 | * @return {Boolean} bool 是否存在
26 | */
27 | function arrayIncludes(arr, item) {
28 | const len = arr.length;
29 | let i = 0;
30 | while (i < len) {
31 | if (arr[i] === item) {
32 | return true;
33 | }
34 | i++;
35 | }
36 | return false;
37 | }
38 |
39 | /**
40 | * 过滤对象不需要的字段
41 | * @param {Array} filterKeys 需要过滤的key
42 | * @param {Object} originObject 原始对象
43 | * @return {Object} buildObject 返回的新对象
44 | */
45 | function filterObjectKey(originObject, filterKeys) {
46 | const buildObject = {};
47 | const keys = Object.keys(originObject).filter(key => filterKeys.indexOf(key) === -1);
48 | if (!keys.length) {
49 | return null;
50 | }
51 | keys.forEach((key) => {
52 | buildObject[key] = originObject[key];
53 | });
54 | return buildObject;
55 | }
56 |
57 | export default {
58 | isFunction,
59 | isArray,
60 | arrayIncludes,
61 | filterObjectKey,
62 | };
63 |
--------------------------------------------------------------------------------
/src/component/axis/index.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, field, enable } = props;
6 | if (chart && field) {
7 | if (enable === false) {
8 | chart.axis(field, false);
9 | } else {
10 | const filterProps = filterObjectKey(props, ['field', 'chart', 'enable']);
11 | chart.axis(field, filterProps);
12 | }
13 | }
14 | return null;
15 | };
16 |
--------------------------------------------------------------------------------
/src/component/chart/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import F2 from '../../f2';
3 | import util from '../../common/util';
4 | // 全局注册精细动画
5 |
6 | /**
7 | * Chart组件
8 | * (chart初始化) (forceUpdate) (执行子组件) (设置数据、动画、度量渲染图表)
9 | * 首次渲染: render --> componentDidMount --> render --> renderChildren --> componentDidUpdate
10 | *
11 | * (执行子组件) (设置数据、动画、度量渲染图表)
12 | * 动态渲染: render --> renderChildren --> componentDidUpdate
13 | */
14 |
15 | class Chart extends Component {
16 | componentDidMount() {
17 | const {
18 | pixelRatio, padding, width, height,
19 | appendPadding,
20 | } = this.props;
21 |
22 | const chart = new F2.Chart({
23 | el: this.refs.canvas,
24 | width,
25 | height,
26 | padding,
27 | pixelRatio,
28 | appendPadding,
29 | });
30 | this.chart = chart;
31 | this.chartRendered = false;
32 | // 手动触发
33 | this.forceUpdate();
34 | }
35 | // 子组件更新完成
36 | componentDidUpdate() {
37 | const {
38 | onPreRender, onRendered, source, scales, animate,
39 | } = this.props;
40 | const { isFunction } = util;
41 | // 防御没有值的情况
42 | if (!source) {
43 | return;
44 | }
45 | // 设置数据、动画、度量
46 | this.chart.animate(animate);
47 | this.chart.source(source, scales);
48 |
49 | // f2准备渲染之前
50 | if (isFunction(onPreRender)) {
51 | onPreRender(this.chart);
52 | }
53 |
54 | if (this.chartRendered) {
55 | this.chart.set('rendered', true);
56 | this.chart.changeData(source);
57 | } else {
58 | this.chart.render();
59 | this.chartRendered = true;
60 | }
61 |
62 | // f2渲染之后,触发一个回调,传递chart给外部
63 | if (isFunction(onRendered)) {
64 | onRendered(this.chart);
65 | }
66 | }
67 | // 更新子组件,实际上是执行f2图形化语法。
68 | renderChildren() {
69 | const { chart } = this;
70 | if (this.chartRendered) {
71 | chart.clear();
72 | }
73 | return React.Children.map(this.props.children, (child) => {
74 | // 添加空判断,child有可能是null
75 | return child && React.cloneElement(child, {
76 | chart, // 子组件传入chartprops
77 | });
78 | });
79 | }
80 | render() {
81 | return (
82 |
83 |
84 | {
85 | this.chart && this.renderChildren()
86 | }
87 |
88 | );
89 | }
90 | }
91 |
92 | export default Chart;
93 |
--------------------------------------------------------------------------------
/src/component/coord/index.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, type } = props;
6 | const filterProps = filterObjectKey(props, ['chart', 'type']);
7 | if (chart && type) {
8 | if (filterProps) {
9 | chart.coord(type, filterProps);
10 | } else {
11 | chart.coord(type);
12 | }
13 | }
14 | return null;
15 | };
16 |
--------------------------------------------------------------------------------
/src/component/geom/area.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { isFunction, isArray, filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, config } = props;
6 | const geom = chart.area(config);
7 | const filterProps = filterObjectKey(props, ['chart']);
8 |
9 | for (const key in filterProps) {
10 | const fn = geom[key];
11 | const value = props[key];
12 | if (isFunction(fn)) {
13 | if (isArray(value)) {
14 | fn.apply(geom, value);
15 | } else {
16 | geom[key](value);
17 | }
18 | }
19 | }
20 | return null;
21 | };
22 |
--------------------------------------------------------------------------------
/src/component/geom/interval.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { isFunction, isArray, filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, config } = props;
6 | const geom = chart.interval(config);
7 | const filterProps = filterObjectKey(props, ['chart']);
8 |
9 | for (const key in filterProps) {
10 | const fn = geom[key];
11 | const value = props[key];
12 | if (isFunction(fn)) {
13 | if (isArray(value)) {
14 | fn.apply(geom, value);
15 | } else {
16 | geom[key](value);
17 | }
18 | }
19 | }
20 | return null;
21 | };
22 |
--------------------------------------------------------------------------------
/src/component/geom/line.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { isFunction, isArray, filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, config } = props;
6 | const geom = chart.line(config);
7 | const filterProps = filterObjectKey(props, ['chart']);
8 |
9 | for (const key in filterProps) {
10 | const fn = geom[key];
11 | const value = props[key];
12 | if (isFunction(fn)) {
13 | if (isArray(value)) {
14 | fn.apply(geom, value);
15 | } else {
16 | geom[key](value);
17 | }
18 | }
19 | }
20 | return null;
21 | };
22 |
--------------------------------------------------------------------------------
/src/component/geom/point.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { isFunction, isArray, filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, config } = props;
6 | const geom = chart.point(config);
7 | const filterProps = filterObjectKey(props, ['chart']);
8 |
9 | for (const key in filterProps) {
10 | const fn = geom[key];
11 | const value = props[key];
12 | if (isFunction(fn)) {
13 | if (isArray(value)) {
14 | fn.apply(geom, value);
15 | } else {
16 | geom[key](value);
17 | }
18 | }
19 | }
20 | return null;
21 | };
22 |
--------------------------------------------------------------------------------
/src/component/guide/index.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { isFunction, filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, type } = props;
6 | if (chart && type) {
7 | const guide = chart.guide();
8 | if (!isFunction(guide[type])) return null;
9 | const filterProps = filterObjectKey(props, ['chart', 'type']);
10 | guide[type](filterProps);
11 | }
12 | return null;
13 | };
14 |
--------------------------------------------------------------------------------
/src/component/legend/index.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, enable } = props;
6 | if (enable === false) {
7 | chart.legend(enable);
8 | } else {
9 | const filterProps = filterObjectKey(props, ['chart', 'enable']);
10 | chart.legend(filterProps);
11 | }
12 | return null;
13 | };
14 |
--------------------------------------------------------------------------------
/src/component/scale/index.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, field } = props;
6 | if (chart && field) {
7 | const filterProps = filterObjectKey(props, ['field', 'chart']);
8 | chart.scale(field, filterProps);
9 | }
10 | return null;
11 | };
12 |
--------------------------------------------------------------------------------
/src/component/tooltip/index.js:
--------------------------------------------------------------------------------
1 | import util from '../../common/util';
2 | const { filterObjectKey } = util;
3 |
4 | export default (props) => {
5 | const { chart, enable } = props;
6 | if (enable === false) {
7 | chart.tooltip(enable);
8 | } else {
9 | const filterProps = filterObjectKey(props, ['chart', 'enable']);
10 | chart.tooltip(filterProps);
11 | }
12 | return null;
13 | };
14 |
--------------------------------------------------------------------------------
/src/f2.js:
--------------------------------------------------------------------------------
1 | import F2 from '@antv/f2';
2 | export default F2;
3 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import F2 from './f2';
2 | import Chart from './component/chart';
3 | import Axis from './component/axis';
4 | import Scale from './component/scale';
5 | import Coord from './component/coord';
6 | import Line from './component/geom/line';
7 | import Point from './component/geom/point';
8 | import Area from './component/geom/area';
9 | import Interval from './component/geom/interval';
10 | import Legend from './component/legend';
11 | import Guide from './component/guide';
12 | import Tooltip from './component/tooltip';
13 | import util from './util';
14 |
15 | export {
16 | F2,
17 | Chart,
18 | Axis,
19 | Scale,
20 | Coord,
21 | Line,
22 | Point,
23 | Area,
24 | Interval,
25 | Legend,
26 | Guide,
27 | Tooltip,
28 | util,
29 | };
30 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | import util from './common/util';
2 | export default util;
3 |
--------------------------------------------------------------------------------
/test/common/util.test.js:
--------------------------------------------------------------------------------
1 | import { util } from '../../src';
2 |
3 | const {
4 | isFunction, isArray, arrayIncludes, filterObjectKey,
5 | } = util;
6 |
7 | describe('工具方法', () => {
8 | describe('isFunction', () => {
9 | it('传undefined', () => {
10 | expect(isFunction(undefined)).toBe(false);
11 | });
12 |
13 | it('传对象{}', () => {
14 | expect(isFunction({})).toBe(false);
15 | });
16 |
17 | it('传方法() => {}', () => {
18 | expect(isFunction(() => {})).toBe(true);
19 | });
20 | });
21 |
22 | describe('isArray', () => {
23 | it('传undefined', () => {
24 | expect(isArray(undefined)).toBe(false);
25 | });
26 |
27 | it('传对象{}', () => {
28 | expect(isArray({})).toBe(false);
29 | });
30 |
31 | it('传数组[]', () => {
32 | expect(isArray([])).toBe(true);
33 | });
34 | });
35 |
36 |
37 | describe('arrayIncludes', () => {
38 | it('数字数组测试存在', () => {
39 | expect(arrayIncludes([1, 2, 3, 4], 3)).toBe(true);
40 | });
41 |
42 | it('数字数组测试不存在', () => {
43 | expect(arrayIncludes([1, 2, 3, 4], 5)).toBe(false);
44 | });
45 |
46 | it('对象数组测试', () => {
47 | const array = [{ count: 1 }, { count: 2 }];
48 | expect(arrayIncludes(array, array[1])).toBe(true);
49 | });
50 |
51 | it('对象数组测试,数组外元素比较', () => {
52 | const array = [{ count: 1 }, { count: 2 }];
53 | expect(arrayIncludes(array, { count: 2 })).toBe(false);
54 | });
55 | });
56 |
57 |
58 | describe('filterObjectKey', () => {
59 | const originData = {
60 | key1: 'key1',
61 | key2: 'key2',
62 | key3: 'key3',
63 | key4: 'key4',
64 | };
65 | it('正确过滤', () => {
66 | expect(filterObjectKey(originData, ['key1', 'key2'])).toEqual({
67 | key3: 'key3',
68 | key4: 'key4',
69 | });
70 | });
71 |
72 | it('返回null', () => {
73 | expect(filterObjectKey(originData, ['key1', 'key2', 'key3', 'key4'])).toEqual(null);
74 | });
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/test/component/area.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Area } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | fileds: null,
11 | color1: null,
12 | color2: null,
13 | position(fileds) {
14 | this.fileds = fileds;
15 | },
16 | color(color1, color2) {
17 | this.color1 = color1;
18 | this.color2 = color2;
19 | },
20 | clear() {
21 | this.fileds = null;
22 | this.color1 = null;
23 | this.color2 = null;
24 | },
25 | };
26 |
27 | const chartWrapper = {
28 | geomConfig: null,
29 | area(geomConfig) {
30 | this.geomConfig = geomConfig;
31 | return mockChart;
32 | },
33 | clear() {
34 | this.geomConfig = null;
35 | },
36 | };
37 |
38 | describe('Area测试', () => {
39 | afterEach((() => {
40 | mockChart.clear();
41 | chartWrapper.clear();
42 | }));
43 | test('不设置props', () => {
44 | const wrapper = shallow();
45 | expect(chartWrapper.geomConfig).toBeUndefined();
46 | expect(mockChart.fileds).toBeNull();
47 | expect(mockChart.color1).toBeNull();
48 | expect(mockChart.color2).toBeNull();
49 | expect(wrapper.html()).toBeNull();
50 | });
51 |
52 |
53 | test('设置了props属性和geomConfig', () => {
54 | const wrapper = shallow();
55 | expect(chartWrapper.geomConfig).toBe('config');
56 | expect(mockChart.fileds).toBe('name*count');
57 | expect(mockChart.color1).toBe(1);
58 | expect(mockChart.color2).toBe(2);
59 | expect(wrapper.html()).toBeNull();
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/component/axis.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Axis } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | field: null,
11 | config: null,
12 | axis(field, config) {
13 | this.field = field;
14 | this.config = config;
15 | },
16 | clear() {
17 | this.field = null;
18 | this.config = null;
19 | },
20 | };
21 |
22 | describe('Axis测试', () => {
23 | afterEach((() => {
24 | mockChart.clear();
25 | }));
26 | test('不设置props', () => {
27 | const wrapper = shallow();
28 | expect(mockChart.field).toBeNull();
29 | expect(mockChart.config).toBeNull();
30 | expect(wrapper.html()).toBeNull();
31 | });
32 |
33 | test('只设置了filed', () => {
34 | const wrapper = shallow();
35 | expect(mockChart.field).toBe('name');
36 | expect(mockChart.config).toEqual(null);
37 | expect(wrapper.html()).toBeNull();
38 | });
39 |
40 |
41 | test('设置了props属性', () => {
42 | const wrapper = shallow();
43 | expect(mockChart.field).toBe('name');
44 | expect(mockChart.config).toEqual({ label: 'label' });
45 | expect(wrapper.html()).toBeNull();
46 | });
47 |
48 | test('设置了enable false', () => {
49 | const wrapper = shallow((
50 |
57 | ));
58 | expect(mockChart.field).toBe('name');
59 | expect(mockChart.config).toEqual(false);
60 | expect(wrapper.html()).toBeNull();
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/test/component/chart.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Chart, Line } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { mount } = Enzyme;
8 |
9 | const data = [{ name: '苹果', count: 5 }, { name: '香蕉', count: 3 }];
10 | describe('Chart组件', () => {
11 | it('canvas设置block样式', () => {
12 | const wrapper = mount();
13 | expect(wrapper.find('canvas').props()).toEqual({ style: { display: 'block' } });
14 | });
15 |
16 | it('生命周期回调', () => {
17 | let onRenderedFlag = false;
18 | let onPreRenderFlag = false;
19 | mount((
20 | { onRenderedFlag = true; }}
23 | onPreRender={() => { onPreRenderFlag = true; }}
24 | >
25 |
26 |
27 | ));
28 | expect(onRenderedFlag).toBe(true);
29 | expect(onPreRenderFlag).toBe(true);
30 | });
31 |
32 | it('Chart参数', () => {
33 | mount((
34 | {
44 | expect(chart.get('width')).toBe('1088');
45 | expect(chart.get('height')).toBe('588');
46 | expect(chart.get('pixelRatio')).toBe(3);
47 | expect(chart.get('scales').name.range).toEqual([0.1, 0.55]);
48 | expect(chart.get('padding')).toEqual([0, 20]);
49 | expect(chart.get('appendPadding')).toEqual([20, 20]);
50 | expect(chart.get('animate')).toBe(false);
51 | }}
52 | >
53 |
54 | { null }
55 |
56 | ));
57 | });
58 |
59 | it('changeData动画', () => {
60 | const component = mount((
61 | {
64 | expect(chart.get('isUpdate')).toBe(undefined);
65 | }}
66 | >
67 |
68 |
69 | ));
70 | const data2 = [{ name: '苹果', count: 5 }, { name: '香蕉', count: 10 }];
71 | component.setProps({
72 | source: data2,
73 | onRender: (chart) => {
74 | expect(chart.get('isUpdate')).toBe(true);
75 | },
76 | });
77 | });
78 |
79 | it('防御source为空', () => {
80 | let chartObj = null;
81 | mount((
82 | {
84 | chartObj = {};
85 | }}
86 | >
87 |
88 |
89 | ));
90 | expect(chartObj).toBeNull();
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/test/component/coord.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Coord } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | type: null,
11 | config: null,
12 | coord(type, config) {
13 | this.type = type;
14 | this.config = config;
15 | },
16 | clear() {
17 | this.type = null;
18 | this.config = null;
19 | },
20 | };
21 |
22 | describe('Coord测试', () => {
23 | afterEach((() => {
24 | mockChart.clear();
25 | }));
26 |
27 | test('不设置props', () => {
28 | const wrapper = shallow();
29 | expect(mockChart.type).toBeNull();
30 | expect(mockChart.config).toBeNull();
31 | expect(wrapper.html()).toBeNull();
32 | });
33 |
34 | test('只设置了type', () => {
35 | const wrapper = shallow();
36 | expect(mockChart.type).toBe('rect');
37 | expect(mockChart.config).toBeUndefined();
38 | expect(wrapper.html()).toBeNull();
39 | });
40 |
41 | test('设置了props属性', () => {
42 | const wrapper = shallow();
43 | expect(mockChart.type).toBe('rect');
44 | expect(mockChart.config).toEqual({ label: 'label' });
45 | expect(wrapper.html()).toBeNull();
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/test/component/guide.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Guide } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | config: null,
11 | tag(config) {
12 | this.config = config;
13 | },
14 | clear() {
15 | this.config = null;
16 | },
17 | };
18 |
19 | const guideWrapper = {
20 | plugin: null,
21 | registerPlugins(plugin) {
22 | this.plugin = plugin;
23 | },
24 | guide() {
25 | return mockChart;
26 | },
27 | clear() {
28 | this.plugin = null;
29 | },
30 | };
31 |
32 |
33 | describe('Guide测试', () => {
34 | afterEach((() => {
35 | mockChart.clear();
36 | guideWrapper.clear();
37 | }));
38 | test('不设置props', () => {
39 | const wrapper = shallow();
40 | expect(guideWrapper.plugin).toBeNull();
41 | expect(mockChart.config).toBeNull();
42 | expect(wrapper.html()).toBeNull();
43 | });
44 |
45 | test('设置了tag', () => {
46 | const wrapper = shallow();
47 | expect(guideWrapper.plugin).not.toBeNull();
48 | expect(mockChart.config).toEqual({ offsetX: 'offsetX', offsetY: 'offsetY' });
49 | expect(wrapper.html()).toBeNull();
50 | });
51 |
52 | test('设置了不存在的组件', () => {
53 | const wrapper = shallow();
54 | expect(guideWrapper.plugin).not.toBeNull();
55 | expect(mockChart.config).toBeNull();
56 | expect(wrapper.html()).toBeNull();
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/test/component/interval.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Interval } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | fileds: null,
11 | color1: null,
12 | color2: null,
13 | position(fileds) {
14 | this.fileds = fileds;
15 | },
16 | color(color1, color2) {
17 | this.color1 = color1;
18 | this.color2 = color2;
19 | },
20 | clear() {
21 | this.fileds = null;
22 | this.color1 = null;
23 | this.color2 = null;
24 | },
25 | };
26 |
27 | const chartWrapper = {
28 | geomConfig: null,
29 | interval(geomConfig) {
30 | this.geomConfig = geomConfig;
31 | return mockChart;
32 | },
33 | clear() {
34 | this.geomConfig = null;
35 | },
36 | };
37 |
38 | describe('Interval组件', () => {
39 | afterEach((() => {
40 | mockChart.clear();
41 | chartWrapper.clear();
42 | }));
43 | test('不设置props', () => {
44 | const wrapper = shallow();
45 | expect(chartWrapper.geomConfig).toBeUndefined();
46 | expect(mockChart.fileds).toBeNull();
47 | expect(mockChart.color1).toBeNull();
48 | expect(mockChart.color2).toBeNull();
49 | expect(wrapper.html()).toBeNull();
50 | });
51 |
52 |
53 | test('设置了props属性和geomConfig', () => {
54 | const wrapper = shallow();
55 | expect(chartWrapper.geomConfig).toBe('config');
56 | expect(mockChart.fileds).toBe('name*count');
57 | expect(mockChart.color1).toBe(1);
58 | expect(mockChart.color2).toBe(2);
59 | expect(wrapper.html()).toBeNull();
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/component/legend.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Legend } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | config: null,
11 | plugin: null,
12 | legend(config) {
13 | this.config = config;
14 | },
15 | registerPlugins(plugin) {
16 | this.plugin = plugin;
17 | },
18 | clear() {
19 | this.plugin = null;
20 | this.config = null;
21 | },
22 | };
23 |
24 | describe('Legend测试', () => {
25 | afterEach((() => {
26 | mockChart.clear();
27 | }));
28 | test('不设置props', () => {
29 | const wrapper = shallow();
30 | expect(mockChart.plugin).not.toBeNull();
31 | expect(mockChart.config).toBeNull();
32 | expect(wrapper.html()).toBeNull();
33 | });
34 |
35 | test('设置了props属性', () => {
36 | const wrapper = shallow();
37 | expect(mockChart.plugin).not.toBeNull();
38 | expect(mockChart.config).toEqual({ offsetX: 'offsetX', offsetY: 'offsetY' });
39 | expect(wrapper.html()).toBeNull();
40 | });
41 |
42 |
43 | test('设置了enable false', () => {
44 | const wrapper = shallow((
45 |
52 | ));
53 | expect(mockChart.plugin).not.toBeNull();
54 | expect(mockChart.config).toEqual(false);
55 | expect(wrapper.html()).toBeNull();
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/test/component/line.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Line } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | fileds: null,
11 | color1: null,
12 | color2: null,
13 | position(fileds) {
14 | this.fileds = fileds;
15 | },
16 | color(color1, color2) {
17 | this.color1 = color1;
18 | this.color2 = color2;
19 | },
20 | clear() {
21 | this.fileds = null;
22 | this.color1 = null;
23 | this.color2 = null;
24 | },
25 | };
26 |
27 | const chartWrapper = {
28 | geomConfig: null,
29 | line(geomConfig) {
30 | this.geomConfig = geomConfig;
31 | return mockChart;
32 | },
33 | clear() {
34 | this.geomConfig = null;
35 | },
36 | };
37 |
38 | describe('Line测试', () => {
39 | afterEach((() => {
40 | mockChart.clear();
41 | chartWrapper.clear();
42 | }));
43 | test('不设置props', () => {
44 | const wrapper = shallow();
45 | expect(chartWrapper.geomConfig).toBeUndefined();
46 | expect(mockChart.fileds).toBeNull();
47 | expect(mockChart.color1).toBeNull();
48 | expect(mockChart.color2).toBeNull();
49 | expect(wrapper.html()).toBeNull();
50 | });
51 |
52 |
53 | test('设置了props属性和geomConfig', () => {
54 | const wrapper = shallow();
55 | expect(chartWrapper.geomConfig).toBe('config');
56 | expect(mockChart.fileds).toBe('name*count');
57 | expect(mockChart.color1).toBe(1);
58 | expect(mockChart.color2).toBe(2);
59 | expect(wrapper.html()).toBeNull();
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/component/point.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Point } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | fileds: null,
11 | color1: null,
12 | color2: null,
13 | position(fileds) {
14 | this.fileds = fileds;
15 | },
16 | color(color1, color2) {
17 | this.color1 = color1;
18 | this.color2 = color2;
19 | },
20 | clear() {
21 | this.fileds = null;
22 | this.color1 = null;
23 | this.color2 = null;
24 | },
25 | };
26 |
27 | const chartWrapper = {
28 | geomConfig: null,
29 | point(geomConfig) {
30 | this.geomConfig = geomConfig;
31 | return mockChart;
32 | },
33 | clear() {
34 | this.geomConfig = null;
35 | },
36 | };
37 |
38 | describe('Point组件', () => {
39 | afterEach((() => {
40 | mockChart.clear();
41 | chartWrapper.clear();
42 | }));
43 | test('不设置props', () => {
44 | const wrapper = shallow();
45 | expect(chartWrapper.geomConfig).toBeUndefined();
46 | expect(mockChart.fileds).toBeNull();
47 | expect(mockChart.color1).toBeNull();
48 | expect(mockChart.color2).toBeNull();
49 | expect(wrapper.html()).toBeNull();
50 | });
51 |
52 |
53 | test('设置了props属性和geomConfig', () => {
54 | const wrapper = shallow();
55 | expect(chartWrapper.geomConfig).toBe('config');
56 | expect(mockChart.fileds).toBe('name*count');
57 | expect(mockChart.color1).toBe(1);
58 | expect(mockChart.color2).toBe(2);
59 | expect(wrapper.html()).toBeNull();
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/test/component/scale.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Scale } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | field: null,
11 | config: null,
12 | scale(field, config) {
13 | this.field = field;
14 | this.config = config;
15 | },
16 | clear() {
17 | this.field = null;
18 | this.config = null;
19 | },
20 | };
21 |
22 | describe('Scale测试', () => {
23 | afterEach((() => {
24 | mockChart.clear();
25 | }));
26 | test('不设置props', () => {
27 | const wrapper = shallow();
28 | expect(mockChart.field).toBeNull();
29 | expect(mockChart.config).toBeNull();
30 | expect(wrapper.html()).toBeNull();
31 | });
32 |
33 | test('只设置了filed', () => {
34 | const wrapper = shallow();
35 | expect(mockChart.field).toBe('name');
36 | expect(mockChart.config).toBeNull();
37 | expect(wrapper.html()).toBeNull();
38 | });
39 |
40 | test('设置了props属性', () => {
41 | const wrapper = shallow();
42 | expect(mockChart.field).toBe('name');
43 | expect(mockChart.config).toEqual({ label: 'label' });
44 | expect(wrapper.html()).toBeNull();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/component/tooltip.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Enzyme from 'enzyme';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { Tooltip } from '../../src';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 | const { shallow } = Enzyme;
8 |
9 | const mockChart = {
10 | config: null,
11 | plugin: null,
12 | tooltip(config) {
13 | this.config = config;
14 | },
15 | registerPlugins(plugin) {
16 | this.plugin = plugin;
17 | },
18 | clear() {
19 | this.plugin = null;
20 | this.config = null;
21 | },
22 | };
23 |
24 | describe('ToolTip测试', () => {
25 | afterEach((() => {
26 | mockChart.clear();
27 | }));
28 | test('不设置props', () => {
29 | const wrapper = shallow();
30 | expect(mockChart.plugin).not.toBeNull();
31 | expect(mockChart.config).toEqual(null);
32 | expect(wrapper.html()).toBeNull();
33 | });
34 |
35 | test('设置了props属性', () => {
36 | const wrapper = shallow();
37 | expect(mockChart.plugin).not.toBeNull();
38 | expect(mockChart.config).toEqual({ offsetX: 'offsetX', offsetY: 'offsetY' });
39 | expect(wrapper.html()).toBeNull();
40 | });
41 |
42 | test('设置了enable false', () => {
43 | const wrapper = shallow((
44 |
51 | ));
52 | expect(mockChart.plugin).not.toBeNull();
53 | expect(mockChart.config).toEqual(false);
54 | expect(wrapper.html()).toBeNull();
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | module: {
6 | rules: [
7 | {
8 | test: /\.(js|jsx)$/,
9 | exclude: /node_modules/,
10 | use: {
11 | loader: 'babel-loader',
12 | },
13 | },
14 | { test: /\.css$/, use: ['style-loader', 'css-loader'] },
15 | ],
16 | },
17 | plugins: [],
18 | externals: {
19 | react: 'React',
20 | 'react-dom': 'ReactDOM',
21 | },
22 | resolve: {
23 | extensions: ['.js', '.jsx'],
24 | },
25 | output: {
26 | filename: 'umd.js',
27 | path: path.resolve(__dirname, 'lib'),
28 | libraryTarget: 'umd',
29 | },
30 | // mode: 'development',
31 | mode: 'production',
32 | };
33 |
--------------------------------------------------------------------------------