├── .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 | ![chart](https://gw.alipayobjects.com/zos/rmsportal/vzSGCkmkOIHlVBZbqoQA.png) 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 (
9 | 10 | 11 |
); 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 | --------------------------------------------------------------------------------