mirror of
https://github.com/hustcc/echarts-for-react.git
synced 2025-12-08 20:16:09 +00:00
chore: init 3.0.0-beta.1
This commit is contained in:
commit
469acda80b
10
.commitlintrc.js
Normal file
10
.commitlintrc.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-angular'],
|
||||
rules: {
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', 'wip'],
|
||||
],
|
||||
},
|
||||
};
|
||||
20
.editorconfig
Normal file
20
.editorconfig
Normal file
@ -0,0 +1,20 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 1
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
4
.eslintignore
Normal file
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist/
|
||||
test
|
||||
build/
|
||||
40
.eslintrc
Normal file
40
.eslintrc
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["prettier", "@typescript-eslint", "import"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"impliedStrict": true
|
||||
},
|
||||
"rules": {
|
||||
"no-sparse-arrays": 0,
|
||||
"no-self-assign": 0,
|
||||
"no-unused-vars": 0, // @typescript-eslint/no-unused-vars
|
||||
"no-inner-declarations": 0,
|
||||
"prettier/prettier": 2,
|
||||
"@typescript-eslint/no-unused-vars": 1,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-use-before-define": [2, { "functions": false }],
|
||||
"@typescript-eslint/ban-ts-ignore": 0,
|
||||
"@typescript-eslint/interface-name-prefix": 0,
|
||||
"@typescript-eslint/no-empty-interface": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/no-inferrable-types": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/type-annotation-spacing": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/ban-types": "off",
|
||||
"no-undef": 0,
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"import/order": 0,
|
||||
"import/no-default-export": 0
|
||||
}
|
||||
}
|
||||
25
.github/workflows/build.yml
vendored
Normal file
25
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: build
|
||||
|
||||
on: ["push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macOS-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Use Node.js 12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.10.0
|
||||
- name: npm install
|
||||
run: |
|
||||
npm install
|
||||
- name: build
|
||||
run: |
|
||||
npm run build
|
||||
env:
|
||||
CI: true
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
83
.gitignore
vendored
Normal file
83
.gitignore
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# lock
|
||||
package-lock.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
yarn.lock
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
public
|
||||
build
|
||||
dist
|
||||
temp
|
||||
.DS_Store
|
||||
.idea
|
||||
.cache
|
||||
demos/assets/screenshots
|
||||
/lib
|
||||
/esm
|
||||
/dist
|
||||
|
||||
*.sw*
|
||||
*.un~
|
||||
|
||||
.vscode
|
||||
/stats.json
|
||||
.umi
|
||||
.umi-production
|
||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
||||
stats.json
|
||||
.cache
|
||||
dist
|
||||
.umi
|
||||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"printWidth": 120,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
23
.umirc.js
Normal file
23
.umirc.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { defineConfig } from 'dumi';
|
||||
|
||||
export default defineConfig({
|
||||
mode: 'site',
|
||||
title: '\b',
|
||||
base: '/',
|
||||
publicPath: process.env.NODE_ENV === 'production' ? '/echarts-for-react/' : '/',
|
||||
exportStatic: {},
|
||||
logo: 'https://cdn.jsdelivr.net/gh/apache/echarts-website@asf-site/zh/images/logo.png?_v_=20200710_1',
|
||||
styles: [
|
||||
'.__dumi-default-navbar-logo:not([data-plaintext]) { padding-left: 200px!important; }',
|
||||
'.echarts-for-react.class_1 { height: 400px!important; }',
|
||||
'.echarts-for-react.class_2 { height: 500px!important; }',
|
||||
],
|
||||
navs: [
|
||||
null,
|
||||
{ title: '在线文档', path: 'https://github.com/hustcc/echarts-for-react' },
|
||||
{ title: 'GitHub', path: 'https://github.com/hustcc/echarts-for-react' },
|
||||
],
|
||||
analytics: {
|
||||
},
|
||||
// more config: https://d.umijs.org/config
|
||||
});
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 AntV team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do { so, subject } to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
306
README.md
Normal file
306
README.md
Normal file
@ -0,0 +1,306 @@
|
||||
# echarts-for-react
|
||||
|
||||
A very simple React wrapper for [Apache ECharts (incubating)](https://github.com/apache/incubator-echarts).
|
||||
|
||||
[](https://github.com/hustcc/echarts-for-react/actions?query=workflow%3Abuild)
|
||||
[](https://coveralls.io/github/hustcc/echarts-for-react)
|
||||
[](https://www.npmjs.com/package/echarts-for-react)
|
||||
[](https://www.npmjs.com/package/echarts-for-react)
|
||||
[](https://www.npmjs.com/package/echarts-for-react)
|
||||

|
||||

|
||||
|
||||
|
||||
# 1. install
|
||||
|
||||
```sh
|
||||
npm install --save echarts-for-react
|
||||
|
||||
# `echarts` is the peerDependence of `echarts-for-react`, you can install echarts with your own version.
|
||||
npm install --save echarts
|
||||
```
|
||||
|
||||
Then use it.
|
||||
|
||||
```js
|
||||
import ReactEcharts from 'echarts-for-react';
|
||||
|
||||
// render echarts option.
|
||||
<ReactEcharts option={this.getOption()} />
|
||||
```
|
||||
|
||||
You can run demo by:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:hustcc/echarts-for-react.git
|
||||
|
||||
npm install
|
||||
|
||||
npm start
|
||||
```
|
||||
|
||||
then open [http://127.0.0.1:8080/](http://127.0.0.1:8080/) in your browser. or see [http://git.hust.cc/echarts-for-react/](http://git.hust.cc/echarts-for-react/)
|
||||
|
||||
## GL
|
||||
|
||||
Install and import [`echarts-gl`](https://www.npmjs.com/package/echarts-gl) module when you want to create a [GL instance](https://www.echartsjs.com/examples/zh/index.html#chart-type-globe)
|
||||
|
||||
- **`Install`**
|
||||
|
||||
```sh
|
||||
npm install --save echarts-gl
|
||||
```
|
||||
|
||||
- **`Import`**
|
||||
|
||||
```js
|
||||
import 'echarts-gl'
|
||||
import ReactEcharts from "echarts-for-react";
|
||||
|
||||
<ReactEcharts
|
||||
option={GL_OPTION}
|
||||
/>
|
||||
|
||||
```
|
||||
|
||||
|
||||
# 2. usage
|
||||
|
||||
Code of a simple demo code showed below. For more example can see: [http://git.hust.cc/echarts-for-react/](http://git.hust.cc/echarts-for-react/)
|
||||
|
||||
### javascript
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import ReactEcharts from 'echarts-for-react'; // or var ReactEcharts = require('echarts-for-react');
|
||||
|
||||
<ReactEcharts
|
||||
option={this.getOption()}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
theme={"theme_name"}
|
||||
onChartReady={this.onChartReadyCallback}
|
||||
onEvents={EventsDict}
|
||||
opts={} />
|
||||
```
|
||||
|
||||
### typescript
|
||||
|
||||
```js
|
||||
import * as React from "react";
|
||||
import ReactEcharts from "echarts-for-react";
|
||||
|
||||
<ReactEcharts
|
||||
option={this.getOption()}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
theme={"theme_name"}
|
||||
onChartReady={this.onChartReadyCallback}
|
||||
onEvents={EventsDict}
|
||||
opts={} />
|
||||
```
|
||||
|
||||
Import ECharts.js modules manually to reduce bundle size
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
// import the core library.
|
||||
import ReactEchartsCore from 'echarts-for-react/lib/core';
|
||||
|
||||
// then import echarts modules those you have used manually.
|
||||
import echarts from 'echarts/lib/echarts';
|
||||
// import 'echarts/lib/chart/line';
|
||||
import 'echarts/lib/chart/bar';
|
||||
// import 'echarts/lib/chart/pie';
|
||||
// import 'echarts/lib/chart/scatter';
|
||||
// import 'echarts/lib/chart/radar';
|
||||
|
||||
// import 'echarts/lib/chart/map';
|
||||
// import 'echarts/lib/chart/treemap';
|
||||
// import 'echarts/lib/chart/graph';
|
||||
// import 'echarts/lib/chart/gauge';
|
||||
// import 'echarts/lib/chart/funnel';
|
||||
// import 'echarts/lib/chart/parallel';
|
||||
// import 'echarts/lib/chart/sankey';
|
||||
// import 'echarts/lib/chart/boxplot';
|
||||
// import 'echarts/lib/chart/candlestick';
|
||||
// import 'echarts/lib/chart/effectScatter';
|
||||
// import 'echarts/lib/chart/lines';
|
||||
// import 'echarts/lib/chart/heatmap';
|
||||
|
||||
// import 'echarts/lib/component/graphic';
|
||||
// import 'echarts/lib/component/grid';
|
||||
// import 'echarts/lib/component/legend';
|
||||
import 'echarts/lib/component/tooltip';
|
||||
// import 'echarts/lib/component/polar';
|
||||
// import 'echarts/lib/component/geo';
|
||||
// import 'echarts/lib/component/parallel';
|
||||
// import 'echarts/lib/component/singleAxis';
|
||||
// import 'echarts/lib/component/brush';
|
||||
|
||||
import 'echarts/lib/component/title';
|
||||
|
||||
// import 'echarts/lib/component/dataZoom';
|
||||
// import 'echarts/lib/component/visualMap';
|
||||
|
||||
// import 'echarts/lib/component/markPoint';
|
||||
// import 'echarts/lib/component/markLine';
|
||||
// import 'echarts/lib/component/markArea';
|
||||
|
||||
// import 'echarts/lib/component/timeline';
|
||||
// import 'echarts/lib/component/toolbox';
|
||||
|
||||
// import 'zrender/lib/vml/vml';
|
||||
|
||||
// The usage of ReactEchartsCore are same with above.
|
||||
<ReactEchartsCore
|
||||
echarts={echarts}
|
||||
option={this.getOption()}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
theme={"theme_name"}
|
||||
onChartReady={this.onChartReadyCallback}
|
||||
onEvents={EventsDict}
|
||||
opts={} />
|
||||
```
|
||||
|
||||
# 3. component props
|
||||
|
||||
- **`option`** (required, object)
|
||||
|
||||
the echarts option config, can see [https://echarts.apache.org/option.html#title](https://echarts.apache.org/option.html#title).
|
||||
|
||||
- **`notMerge`** (optional, object)
|
||||
|
||||
when `setOption`, not merge the data, default is `false`. See [https://echarts.apache.org/api.html#echartsInstance.setOption](https://echarts.apache.org/api.html#echartsInstance.setOption).
|
||||
|
||||
- **`lazyUpdate`** (optional, object)
|
||||
|
||||
when `setOption`, lazy update the data, default is `false`. See [https://echarts.apache.org/api.html#echartsInstance.setOption](https://echarts.apache.org/api.html#echartsInstance.setOption).
|
||||
|
||||
- **`style`** (optional, object)
|
||||
|
||||
the `style` of echarts div. `object`, default is {height: '300px'}.
|
||||
|
||||
- **`className`** (optional, string)
|
||||
|
||||
the `class` of echarts div. you can setting the css style of charts by class name.
|
||||
|
||||
- **`theme`** (optional, string)
|
||||
|
||||
the `theme` of echarts. `string`, should `registerTheme` before use it (theme object format: [https://github.com/ecomfe/echarts/blob/master/theme/dark.js](https://github.com/ecomfe/echarts/blob/master/theme/dark.js)). e.g.
|
||||
|
||||
```js
|
||||
// import echarts
|
||||
import echarts from 'echarts';
|
||||
...
|
||||
// register theme object
|
||||
echarts.registerTheme('my_theme', {
|
||||
backgroundColor: '#f4cccc'
|
||||
});
|
||||
...
|
||||
// render the echarts use option `theme`
|
||||
<ReactEcharts
|
||||
option={this.getOption()}
|
||||
style={{height: '300px', width: '100%'}}
|
||||
className='echarts-for-echarts'
|
||||
theme='my_theme' />
|
||||
```
|
||||
|
||||
- **`onChartReady`** (optional, function)
|
||||
|
||||
when the chart is ready, will callback the function with the `echarts object` as it's paramter.
|
||||
|
||||
- **`loadingOption`** (optional, object)
|
||||
|
||||
the echarts loading option config, can see [https://echarts.apache.org/api.html#echartsInstance.showLoading](https://echarts.apache.org/api.html#echartsInstance.showLoading).
|
||||
|
||||
- **`showLoading`** (optional, bool, default: false)
|
||||
|
||||
`bool`, when the chart is rendering, show the loading mask.
|
||||
|
||||
- **`onEvents`** (optional, array(string=>function) )
|
||||
|
||||
binding the echarts event, will callback with the `echarts event object`, and `the echart object` as it's paramters. e.g:
|
||||
|
||||
```js
|
||||
let onEvents = {
|
||||
'click': this.onChartClick,
|
||||
'legendselectchanged': this.onChartLegendselectchanged
|
||||
}
|
||||
...
|
||||
<ReactEcharts
|
||||
option={this.getOption()}
|
||||
style={{height: '300px', width: '100%'}}
|
||||
onEvents={onEvents} />
|
||||
```
|
||||
for more event key name, see: [https://echarts.apache.org/api.html#events](https://echarts.apache.org/api.html#events)
|
||||
|
||||
- **`opts`** (optional, object)
|
||||
|
||||
the `opts` of echarts. `object`, will be used when initial echarts instance by `echarts.init`. Document [here](https://echarts.apache.org/api.html#echarts.init).
|
||||
|
||||
```js
|
||||
<ReactEcharts
|
||||
option={this.getOption()}
|
||||
style={{height: '300px'}}
|
||||
opts={{renderer: 'svg'}} // use svg to render the chart.
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
# 4. Component API & Echarts API
|
||||
|
||||
the Component only has `one API` named `getEchartsInstance`.
|
||||
|
||||
- **`getEchartsInstance()`** : get the echarts instance object, then you can use any `API of echarts`.
|
||||
|
||||
for example:
|
||||
|
||||
```js
|
||||
// render the echarts component below with rel
|
||||
<ReactEcharts ref={(e) => { this.echarts_react = e; }}
|
||||
option={this.getOption()} />
|
||||
|
||||
// then get the `ReactEcharts` use this.echarts_react
|
||||
|
||||
let echarts_instance = this.echarts_react.getEchartsInstance();
|
||||
// then you can use any API of echarts.
|
||||
let base64 = echarts_instance.getDataURL();
|
||||
```
|
||||
|
||||
**About API of echarts, can see** [https://echarts.apache.org/api.html#echartsInstance](https://echarts.apache.org/api.html#echartsInstance).
|
||||
|
||||
You can use the API to do:
|
||||
|
||||
1. `binding / unbinding` event.
|
||||
2. `dynamic charts` with dynamic data.
|
||||
3. get the echarts dom / dataURL / base64, save the chart to png.
|
||||
4. `release` the charts.
|
||||
|
||||
|
||||
# 5. Q & A
|
||||
|
||||
- How to render the chart with svg when using echarts 4.x
|
||||
|
||||
Use the props `opts` of component with `renderer = 'svg'`. For example:
|
||||
|
||||
|
||||
```js
|
||||
<ReactEcharts
|
||||
option={this.getOption()}
|
||||
style={{height: '300px'}}
|
||||
opts={{renderer: 'svg'}} // use svg to render the chart.
|
||||
/>
|
||||
```
|
||||
|
||||
- How to resolve Error `Component series.scatter3D not exists. Load it first.`
|
||||
|
||||
[Install and import `echarts-gl` first](#GL)
|
||||
|
||||
|
||||
# 6. LICENSE
|
||||
|
||||
MIT@[hustcc](https://github.com/hustcc).
|
||||
|
||||
|
||||
37
__tests__/charts/simple-spec.tsx
Normal file
37
__tests__/charts/simple-spec.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import ReactECharts from '../../src/';
|
||||
import { render, destroy, createDiv, removeDom } from '../utils';
|
||||
|
||||
const options = {
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('chart', () => {
|
||||
it('simple', () => {
|
||||
let instance;
|
||||
const div = createDiv();
|
||||
const Comp = <ReactECharts ref={(e) => (instance = e)} option={options} />;
|
||||
render(Comp, div);
|
||||
|
||||
expect(instance).toBeDefined();
|
||||
expect(instance.getEchartsInstance()).toBeDefined();
|
||||
|
||||
destroy(div);
|
||||
expect(div.querySelector('*')).toBe(null);
|
||||
|
||||
removeDom(div);
|
||||
});
|
||||
});
|
||||
7
__tests__/helper/is-equal-spec.ts
Normal file
7
__tests__/helper/is-equal-spec.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { isEqual } from '../../src/helper/is-equal';
|
||||
|
||||
describe('is-equal', () => {
|
||||
it('isEqual', () => {
|
||||
expect(isEqual({}, {})).toBe(true);
|
||||
});
|
||||
});
|
||||
10
__tests__/helper/is-fucntion-spec.ts
Normal file
10
__tests__/helper/is-fucntion-spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { isFunction } from '../../src/helper/is-function';
|
||||
|
||||
describe('is-function', () => {
|
||||
it('isFunction', () => {
|
||||
expect(isFunction(1)).toBe(false);
|
||||
expect(isFunction('')).toBe(false);
|
||||
expect(isFunction(true)).toBe(false);
|
||||
expect(isFunction(() => {})).toBe(true);
|
||||
});
|
||||
});
|
||||
10
__tests__/helper/is-string-spec.ts
Normal file
10
__tests__/helper/is-string-spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { isString } from '../../src/helper/is-string';
|
||||
|
||||
describe('is-function', () => {
|
||||
it('isFunction', () => {
|
||||
expect(isString(1)).toBe(false);
|
||||
expect(isString('')).toBe(true);
|
||||
expect(isString(true)).toBe(false);
|
||||
expect(isString(() => {})).toBe(false);
|
||||
});
|
||||
});
|
||||
10
__tests__/helper/pick-spec.ts
Normal file
10
__tests__/helper/pick-spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { pick } from '../../src/helper/pick';
|
||||
|
||||
describe('pick', () => {
|
||||
it('pick', () => {
|
||||
expect(pick({ a: 1 }, [])).toEqual({});
|
||||
expect(pick({ a: 1 }, ['b'])).toEqual({});
|
||||
expect(pick({ a: 1 }, ['a'])).toEqual({ a: 1 });
|
||||
expect(pick({ a: 1 }, ['a', 'b'])).toEqual({ a: 1 });
|
||||
});
|
||||
});
|
||||
44
__tests__/utils/index.ts
Normal file
44
__tests__/utils/index.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
/**
|
||||
* 渲染组件
|
||||
* @param comp
|
||||
* @param container
|
||||
*/
|
||||
export function render(comp: any, container: HTMLElement) {
|
||||
ReactDOM.render(comp, container);
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载组件
|
||||
* @param container
|
||||
*/
|
||||
export function destroy(container: HTMLElement) {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 div 节点,并放到 container,默认放到 body 上
|
||||
* @param title
|
||||
* @param container
|
||||
* @param id 容器 id
|
||||
*/
|
||||
export function createDiv(container: HTMLElement = document.body): HTMLElement {
|
||||
const div = document.createElement('div');
|
||||
|
||||
container.appendChild(div);
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 dom 元素
|
||||
* @param dom
|
||||
*/
|
||||
export function removeDom(dom: HTMLElement) {
|
||||
const parent = dom.parentNode;
|
||||
|
||||
if (parent) {
|
||||
parent.removeChild(dom);
|
||||
}
|
||||
}
|
||||
128
docs/examples/api.md
Normal file
128
docs/examples/api.md
Normal file
@ -0,0 +1,128 @@
|
||||
---
|
||||
title: ECharts API
|
||||
order: 2
|
||||
---
|
||||
|
||||
## ECharts API
|
||||
|
||||
```tsx
|
||||
import React, { useRef } from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '漏斗图',
|
||||
subtext: '纯属虚构'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: "{a} <br/>{b} : {c}%"
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataView: {readOnly: false},
|
||||
restore: {},
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: ['展现','点击','访问','咨询','订单']
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '预期',
|
||||
type: 'funnel',
|
||||
left: '10%',
|
||||
width: '80%',
|
||||
label: {
|
||||
normal: {
|
||||
formatter: '{b}预期'
|
||||
},
|
||||
emphasis: {
|
||||
position:'inside',
|
||||
formatter: '{b}预期: {c}%'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
normal: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0.7
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{value: 60, name: '访问'},
|
||||
{value: 40, name: '咨询'},
|
||||
{value: 20, name: '订单'},
|
||||
{value: 80, name: '点击'},
|
||||
{value: 100, name: '展现'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '实际',
|
||||
type: 'funnel',
|
||||
left: '10%',
|
||||
width: '80%',
|
||||
maxSize: '80%',
|
||||
label: {
|
||||
normal: {
|
||||
position: 'inside',
|
||||
formatter: '{c}%',
|
||||
textStyle: {
|
||||
color: '#fff'
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
position:'inside',
|
||||
formatter: '{b}实际: {c}%'
|
||||
}
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0.5,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{value: 30, name: '访问'},
|
||||
{value: 10, name: '咨询'},
|
||||
{value: 5, name: '订单'},
|
||||
{value: 50, name: '点击'},
|
||||
{value: 80, name: '展现'}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const instance = useRef(null);
|
||||
|
||||
function clickBtn() {
|
||||
const base64 = instance.current.getEchartsInstance().getDataURL();
|
||||
|
||||
const img = new Image();
|
||||
img.src = base64;
|
||||
const newWin = window.open('', '_blank');
|
||||
newWin.document.write(img.outerHTML);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactECharts
|
||||
ref={instance}
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
/>
|
||||
<div>
|
||||
<button onClick={clickBtn}>click here to get the DataURL of chart.</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
178
docs/examples/dynamic.md
Normal file
178
docs/examples/dynamic.md
Normal file
@ -0,0 +1,178 @@
|
||||
---
|
||||
title: Dynamic
|
||||
order: 6
|
||||
---
|
||||
|
||||
## Dynamic
|
||||
|
||||
```tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import cloneDeep from 'lodash.clonedeep';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const DEFAULT_OPTION = {
|
||||
title: {
|
||||
text:'Hello Echarts-for-react.',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data:['最新成交价', '预购队列']
|
||||
},
|
||||
toolbox: {
|
||||
show: true,
|
||||
feature: {
|
||||
dataView: {readOnly: false},
|
||||
restore: {},
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: 60,
|
||||
left: 30,
|
||||
right: 60,
|
||||
bottom:30
|
||||
},
|
||||
dataZoom: {
|
||||
show: false,
|
||||
start: 0,
|
||||
end: 100
|
||||
},
|
||||
visualMap: {
|
||||
show: false,
|
||||
min: 0,
|
||||
max: 1000,
|
||||
color: ['#BE002F', '#F20C00', '#F00056', '#FF2D51', '#FF2121', '#FF4C00', '#FF7500',
|
||||
'#FF8936', '#FFA400', '#F0C239', '#FFF143', '#FAFF72', '#C9DD22', '#AFDD22',
|
||||
'#9ED900', '#00E500', '#0EB83A', '#0AA344', '#0C8918', '#057748', '#177CB0']
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
data: (function (){
|
||||
let now = new Date();
|
||||
let res = [];
|
||||
let len = 50;
|
||||
while (len--) {
|
||||
res.unshift(now.toLocaleTimeString().replace(/^\D*/,''));
|
||||
now = new Date(now - 2000);
|
||||
}
|
||||
return res;
|
||||
})()
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: true,
|
||||
data: (function (){
|
||||
let res = [];
|
||||
let len = 50;
|
||||
while (len--) {
|
||||
res.push(50 - len + 1);
|
||||
}
|
||||
return res;
|
||||
})()
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '价格',
|
||||
max: 20,
|
||||
min: 0,
|
||||
boundaryGap: [0.2, 0.2]
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
scale: true,
|
||||
name: '预购量',
|
||||
max: 1200,
|
||||
min: 0,
|
||||
boundaryGap: [0.2, 0.2]
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name:'预购队列',
|
||||
type:'bar',
|
||||
xAxisIndex: 1,
|
||||
yAxisIndex: 1,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderRadius: 4,
|
||||
}
|
||||
},
|
||||
animationEasing: 'elasticOut',
|
||||
animationDelay: function (idx) {
|
||||
return idx * 10;
|
||||
},
|
||||
animationDelayUpdate: function (idx) {
|
||||
return idx * 10;
|
||||
},
|
||||
data:(function (){
|
||||
let res = [];
|
||||
let len = 50;
|
||||
while (len--) {
|
||||
res.push(Math.round(Math.random() * 1000));
|
||||
}
|
||||
return res;
|
||||
})()
|
||||
},
|
||||
{
|
||||
name:'最新成交价',
|
||||
type:'line',
|
||||
data:(function (){
|
||||
let res = [];
|
||||
let len = 0;
|
||||
while (len < 50) {
|
||||
res.push((Math.random()*10 + 5).toFixed(1) - 0);
|
||||
len++;
|
||||
}
|
||||
return res;
|
||||
})()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let count;
|
||||
|
||||
const [option, setOption] = useState(DEFAULT_OPTION);
|
||||
|
||||
function fetchNewData() {
|
||||
const axisData = (new Date()).toLocaleTimeString().replace(/^\D*/,'');
|
||||
const newOption = cloneDeep(option); // immutable
|
||||
newOption.title.text = 'Hello Echarts-for-react.' + new Date().getSeconds();
|
||||
const data0 = newOption.series[0].data;
|
||||
const data1 = newOption.series[1].data;
|
||||
data0.shift();
|
||||
data0.push(Math.round(Math.random() * 1000));
|
||||
data1.shift();
|
||||
data1.push((Math.random() * 10 + 5).toFixed(1) - 0);
|
||||
|
||||
newOption.xAxis[0].data.shift();
|
||||
newOption.xAxis[0].data.push(axisData);
|
||||
newOption.xAxis[1].data.shift();
|
||||
newOption.xAxis[1].data.push(count++);
|
||||
|
||||
setOption(newOption);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
fetchNewData();
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
});
|
||||
|
||||
return <ReactECharts
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
85
docs/examples/event.md
Normal file
85
docs/examples/event.md
Normal file
@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Event
|
||||
order: 4
|
||||
---
|
||||
|
||||
## Event
|
||||
|
||||
```tsx
|
||||
import React, { useState } from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
title : {
|
||||
text: '某站点用户访问来源',
|
||||
subtext: '纯属虚构',
|
||||
x:'center'
|
||||
},
|
||||
tooltip : {
|
||||
trigger: 'item',
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)"
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎']
|
||||
},
|
||||
series : [
|
||||
{
|
||||
name: '访问来源',
|
||||
type: 'pie',
|
||||
radius : '55%',
|
||||
center: ['50%', '60%'],
|
||||
data:[
|
||||
{value:335, name:'直接访问'},
|
||||
{value:310, name:'邮件营销'},
|
||||
{value:234, name:'联盟广告'},
|
||||
{value:135, name:'视频广告'},
|
||||
{value:1548, name:'搜索引擎'}
|
||||
],
|
||||
itemStyle: {
|
||||
emphasis: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
function onChartReady(echarts) {
|
||||
console.log('echarts is ready', echarts);
|
||||
}
|
||||
|
||||
function onChartClick(param, echarts) {
|
||||
console.log(param, echarts);
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
function onChartLegendselectchanged(param, echarts) {
|
||||
console.log(param, echarts);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactECharts
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
onChartReady={onChartReady}
|
||||
onEvents={{
|
||||
'click': onChartClick,
|
||||
'legendselectchanged': onChartLegendselectchanged
|
||||
}}
|
||||
/>
|
||||
<div>Click Count: {count}</div>
|
||||
<div>Open console, see the log detail.</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
36
docs/examples/gl.md
Normal file
36
docs/examples/gl.md
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: Web GL
|
||||
order: 7
|
||||
---
|
||||
|
||||
## Web GL
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import 'echarts-gl';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
grid3D: {},
|
||||
xAxis3D: {},
|
||||
yAxis3D: {},
|
||||
zAxis3D: {},
|
||||
series: [{
|
||||
type: 'scatter3D',
|
||||
symbolSize: 50,
|
||||
data: [[-1, -1, -1], [0, 0, 0], [1, 1, 1]],
|
||||
itemStyle: {
|
||||
opacity: 1
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
return <ReactECharts
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
52
docs/examples/graph.md
Normal file
52
docs/examples/graph.md
Normal file
File diff suppressed because one or more lines are too long
79
docs/examples/loading.md
Normal file
79
docs/examples/loading.md
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Loading
|
||||
order: 5
|
||||
---
|
||||
|
||||
## Loading
|
||||
|
||||
```tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '基础雷达图'
|
||||
},
|
||||
tooltip: {},
|
||||
legend: {
|
||||
data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)']
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ name: '销售(sales)', max: 6500},
|
||||
{ name: '管理(Administration)', max: 16000},
|
||||
{ name: '信息技术(Information Techology)', max: 30000},
|
||||
{ name: '客服(Customer Support)', max: 38000},
|
||||
{ name: '研发(Development)', max: 52000},
|
||||
{ name: '市场(Marketing)', max: 25000}
|
||||
]
|
||||
},
|
||||
series: [{
|
||||
name: '预算 vs 开销(Budget vs spending)',
|
||||
type: 'radar',
|
||||
// areaStyle: {normal: {}},
|
||||
data : [
|
||||
{
|
||||
value : [4300, 10000, 28000, 35000, 50000, 19000],
|
||||
name : '预算分配(Allocated Budget)'
|
||||
},
|
||||
{
|
||||
value : [5000, 14000, 28000, 31000, 42000, 21000],
|
||||
name : '实际开销(Actual Spending)'
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
let timer;
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
|
||||
const loadingOption = {
|
||||
text: '加载中...',
|
||||
color: '#4413c2',
|
||||
textColor: '#270240',
|
||||
maskColor: 'rgba(194, 88, 86, 0.3)',
|
||||
zlevel: 0
|
||||
};
|
||||
|
||||
function onChartReady(echarts) {
|
||||
timer = setTimeout(function() {
|
||||
echarts.hideLoading();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
return <ReactECharts
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
onChartReady={onChartReady}
|
||||
loadingOption={loadingOption}
|
||||
showLoading={true}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
78
docs/examples/simple.md
Normal file
78
docs/examples/simple.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Simple
|
||||
order: 1
|
||||
---
|
||||
|
||||
## 简单堆积面积图
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '堆叠区域图'
|
||||
},
|
||||
tooltip : {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data:['邮件营销','联盟广告','视频广告']
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: {}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis : [
|
||||
{
|
||||
type : 'category',
|
||||
boundaryGap : false,
|
||||
data : ['周一','周二','周三','周四','周五','周六','周日']
|
||||
}
|
||||
],
|
||||
yAxis : [
|
||||
{
|
||||
type : 'value'
|
||||
}
|
||||
],
|
||||
series : [
|
||||
{
|
||||
name:'邮件营销',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data:[120, 132, 101, 134, 90, 230, 210]
|
||||
},
|
||||
{
|
||||
name:'联盟广告',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data:[220, 182, 191, 234, 290, 330, 310]
|
||||
},
|
||||
{
|
||||
name:'视频广告',
|
||||
type:'line',
|
||||
stack: '总量',
|
||||
areaStyle: {normal: {}},
|
||||
data:[150, 232, 201, 154, 190, 330, 410]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return <ReactECharts
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
40
docs/examples/svg.md
Normal file
40
docs/examples/svg.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: SVG
|
||||
order: 8
|
||||
---
|
||||
|
||||
## SVG
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: 'ECharts 入门示例'
|
||||
},
|
||||
tooltip: {},
|
||||
legend: {
|
||||
data:['销量']
|
||||
},
|
||||
xAxis: {
|
||||
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
|
||||
},
|
||||
yAxis: {},
|
||||
series: [{
|
||||
name: '销量',
|
||||
type: 'bar',
|
||||
data: [5, 20, 36, 10, 10, 20]
|
||||
}]
|
||||
};
|
||||
|
||||
return <ReactECharts
|
||||
option={option}
|
||||
style={{ height: 400 }}
|
||||
opts={{ renderer: 'svg' }}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
123
docs/examples/theme.md
Normal file
123
docs/examples/theme.md
Normal file
@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Theme
|
||||
order: 3
|
||||
---
|
||||
|
||||
## Theme
|
||||
|
||||
```tsx
|
||||
import React, { useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
echarts.registerTheme('my_theme', {
|
||||
backgroundColor: '#f4cccc'
|
||||
});
|
||||
echarts.registerTheme('another_theme', {
|
||||
backgroundColor: '#eee'
|
||||
});
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const option = {
|
||||
title: {
|
||||
text: '阶梯瀑布图',
|
||||
subtext: 'From ExcelHome',
|
||||
sublink: 'http://e.weibo.com/1341556070/Aj1J2x5a5'
|
||||
},
|
||||
tooltip : {
|
||||
trigger: 'axis',
|
||||
axisPointer : { // 坐标轴指示器,坐标轴触发有效
|
||||
type : 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data:['支出','收入']
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type : 'category',
|
||||
splitLine: {show:false},
|
||||
data : ["11月1日", "11月2日", "11月3日", "11月4日", "11月5日", "11月6日", "11月7日", "11月8日", "11月9日", "11月10日", "11月11日"]
|
||||
},
|
||||
yAxis: {
|
||||
type : 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '辅助',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
barBorderColor: 'rgba(0,0,0,0)',
|
||||
color: 'rgba(0,0,0,0)'
|
||||
},
|
||||
emphasis: {
|
||||
barBorderColor: 'rgba(0,0,0,0)',
|
||||
color: 'rgba(0,0,0,0)'
|
||||
}
|
||||
},
|
||||
data: [0, 900, 1245, 1530, 1376, 1376, 1511, 1689, 1856, 1495, 1292]
|
||||
},
|
||||
{
|
||||
name: '收入',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
data: [900, 345, 393, '-', '-', 135, 178, 286, '-', '-', '-']
|
||||
},
|
||||
{
|
||||
name: '支出',
|
||||
type: 'bar',
|
||||
stack: '总量',
|
||||
label: {
|
||||
normal: {
|
||||
show: true,
|
||||
position: 'bottom'
|
||||
}
|
||||
},
|
||||
data: ['-', '-', '-', 108, 154, '-', '-', '-', 119, 361, 203]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const [theme, setTheme] = useState();
|
||||
const [className, setClassName] = useState('class_1');
|
||||
|
||||
function toggleTheme() {
|
||||
setTheme(theme === 'my_theme' ? 'another_theme' : 'my_theme');
|
||||
}
|
||||
|
||||
function toggleClassName() {
|
||||
setClassName(className === 'class_1' ? 'class_2' : 'class_1');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactECharts
|
||||
option={option}
|
||||
className={className}
|
||||
theme={theme}
|
||||
style={{ height: 400 }}
|
||||
/>
|
||||
<br />
|
||||
<div>
|
||||
<button onClick={toggleTheme}>Click to Toggle theme.</button>
|
||||
<button onClick={toggleClassName}>Click to Toggle className.</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
86
docs/index.md
Normal file
86
docs/index.md
Normal file
@ -0,0 +1,86 @@
|
||||
---
|
||||
title: ECharts for React - 全网最好用的 ECharts 的 React 组件封装
|
||||
order: 10
|
||||
hero:
|
||||
title: ECharts for React
|
||||
desc: 全网最好用的 ECharts 的 React 组件封装
|
||||
actions:
|
||||
- text: 查看在线 DEMO
|
||||
link: /examples/dynamic
|
||||
footer: Open-source MIT Licensed | Copyright © 2021-present
|
||||
---
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
$ npm install echarts-for-react
|
||||
```
|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
```tsx | pure
|
||||
import React from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const options = {
|
||||
grid: { top: 8, right: 8, bottom: 24, left: 36 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <ReactECharts option={options} />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
|
||||
最终结果:
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const options = {
|
||||
grid: { top: 8, right: 8, bottom: 24, left: 36 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [820, 932, 901, 934, 1290, 1330, 1320],
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <ReactECharts option={options} />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
```
|
||||
|
||||
|
||||
## 反馈
|
||||
|
||||
请访问 [GitHub](https://github.com/hustcc/echarts-for-react)。
|
||||
116
package.json
Normal file
116
package.json
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
"name": "echarts-for-react",
|
||||
"version": "3.0.0-beta.1",
|
||||
"description": " Apache Echarts(v5) components for React.",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib",
|
||||
"esm",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"docs:dev": "dumi dev",
|
||||
"docs:build": "dumi build",
|
||||
"docs:deploy": "rimraf dist && npm run docs:build && gh-pages -d dist",
|
||||
"start": "webpack-dev-server --watch",
|
||||
"lint": "eslint src __tests__ && prettier src __tests__ --check",
|
||||
"fix": "eslint src __tests__ --fix && prettier src __tests__ --write",
|
||||
"clean": "rimraf lib esm dist",
|
||||
"test": "jest",
|
||||
"lib": "run-p lib:*",
|
||||
"lib:cjs": "tsc -p tsconfig.json --target ES5 --module commonjs --outDir lib",
|
||||
"lib:esm": "tsc -p tsconfig.json --target ES5 --module ESNext --outDir esm",
|
||||
"build": "npm run clean && npm run test && npm run lib"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hustcc/echarts-for-react.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"component",
|
||||
"echarts-react",
|
||||
"echarts",
|
||||
"react-echarts",
|
||||
"chart",
|
||||
"charts",
|
||||
"graph",
|
||||
"react-component"
|
||||
],
|
||||
"author": "hustcc (http://github.com/hustcc)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/hustcc/echarts-for-react/issues"
|
||||
},
|
||||
"homepage": "https://github.com/hustcc/echarts-for-react",
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^11.0.0",
|
||||
"@commitlint/config-angular": "^11.0.0",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node": "^14.14.14",
|
||||
"@types/react": "^17.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.2",
|
||||
"@typescript-eslint/parser": "^4.11.0",
|
||||
"cross-env": "^5.1.3",
|
||||
"dumi": "^1.1.4",
|
||||
"dumi-theme-default": "^1.0.4",
|
||||
"echarts": "^5.0.2",
|
||||
"echarts-gl": "^2.0.1",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"gh-pages": "^3.1.0",
|
||||
"husky": "^4.3.8",
|
||||
"jest": "^26.6.3",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
"jest-electron": "^0.1.11",
|
||||
"lint-md-cli": "^0.1.2",
|
||||
"lint-staged": "^10.5.3",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"miz": "^1.0.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.2.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"ts-jest": "^26.5.0",
|
||||
"ts-loader": "^8.0.15",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"size-sensor": "^1.0.0",
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"echarts": "^5.0.0"
|
||||
},
|
||||
"jest": {
|
||||
"runner": "jest-electron/runner",
|
||||
"testEnvironment": "jest-electron/environment",
|
||||
"preset": "ts-jest",
|
||||
"collectCoverage": true,
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.ts",
|
||||
"!**/node_modules/**",
|
||||
"!**/vendor/**"
|
||||
],
|
||||
"testRegex": "/__tests__/.*-spec\\.tsx?$"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged",
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.md": [
|
||||
"lint-md --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
185
src/core.tsx
Normal file
185
src/core.tsx
Normal file
@ -0,0 +1,185 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { bind, clear } from 'size-sensor';
|
||||
import { pick } from './helper/pick';
|
||||
import { isFunction } from './helper/is-function';
|
||||
import { isString } from './helper/is-string';
|
||||
import { isEqual } from './helper/is-equal';
|
||||
import { EChartsReactProps, EChartsInstance } from './types';
|
||||
|
||||
/**
|
||||
* core component for echarts binding
|
||||
*/
|
||||
export default class EChartsReactCore extends PureComponent<EChartsReactProps> {
|
||||
/**
|
||||
* echarts render container
|
||||
*/
|
||||
public ele: HTMLElement;
|
||||
|
||||
/**
|
||||
* echarts library entry
|
||||
*/
|
||||
protected echarts: any;
|
||||
|
||||
constructor(props: EChartsReactProps) {
|
||||
super(props);
|
||||
|
||||
this.echarts = props.echarts;
|
||||
this.ele = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.renderNewEcharts();
|
||||
}
|
||||
|
||||
// update
|
||||
componentDidUpdate(prevProps: EChartsReactProps) {
|
||||
/**
|
||||
* if shouldSetOption return false, then return, not update echarts options
|
||||
* default is true
|
||||
*/
|
||||
const { shouldSetOption } = this.props;
|
||||
if (isFunction(shouldSetOption) && !shouldSetOption(prevProps, this.props)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 以下属性修改的时候,需要 dispose 之后再新建
|
||||
// 1. 切换 theme 的时候
|
||||
// 2. 修改 opts 的时候
|
||||
// 3. 修改 onEvents 的时候,这样可以取消所有之前绑定的事件 issue #151
|
||||
if (
|
||||
!isEqual(prevProps.theme, this.props.theme) ||
|
||||
!isEqual(prevProps.opts, this.props.opts) ||
|
||||
!isEqual(prevProps.onEvents, this.props.onEvents)
|
||||
) {
|
||||
this.dispose();
|
||||
|
||||
this.renderNewEcharts(); // 重建
|
||||
return;
|
||||
}
|
||||
|
||||
// when thoes props isEqual, do not update echarts
|
||||
const pickKeys = ['option', 'notMerge', 'lazyUpdate', 'showLoading', 'loadingOption'];
|
||||
if (isEqual(pick(this.props, pickKeys), pick(prevProps, pickKeys))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const echartsInstance = this.updateEChartsOption();
|
||||
/**
|
||||
* when style or class name updated, change size.
|
||||
*/
|
||||
if (!isEqual(prevProps.style, this.props.style) || !isEqual(prevProps.className, this.props.className)) {
|
||||
try {
|
||||
echartsInstance.resize();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* return the echart object
|
||||
* 1. if exist, return the existed instance
|
||||
* 2. or new one instance
|
||||
*/
|
||||
public getEchartsInstance() {
|
||||
return this.echarts.getInstanceByDom(this.ele) || this.echarts.init(this.ele, this.props.theme, this.props.opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* dispose echarts and clear size-sensor
|
||||
*/
|
||||
private dispose() {
|
||||
if (this.ele) {
|
||||
try {
|
||||
clear(this.ele);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
// dispose echarts instance
|
||||
this.echarts.dispose(this.ele);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render a new echarts instance
|
||||
*/
|
||||
private renderNewEcharts() {
|
||||
const { onEvents, onChartReady } = this.props;
|
||||
|
||||
// 1. new echarts instance
|
||||
const echartsInstance = this.updateEChartsOption();
|
||||
|
||||
// 2. bind events
|
||||
this.bindEvents(echartsInstance, onEvents || {});
|
||||
|
||||
// 3. on chart ready
|
||||
if (isFunction(onChartReady)) onChartReady(echartsInstance);
|
||||
|
||||
// 4. on resize
|
||||
if (this.ele) {
|
||||
bind(this.ele, () => {
|
||||
try {
|
||||
echartsInstance.resize();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// bind the events
|
||||
private bindEvents(instance, events: EChartsReactProps['onEvents']) {
|
||||
function _bindEvent(eventName: string, func: Function) {
|
||||
// ignore the event config which not satisfy
|
||||
if (isString(eventName) && isFunction(func)) {
|
||||
// binding event
|
||||
instance.on(eventName, (param) => {
|
||||
func(param, instance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// loop and bind
|
||||
for (const eventName in events) {
|
||||
if (Object.prototype.hasOwnProperty.call(events, eventName)) {
|
||||
_bindEvent(eventName, events[eventName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render the echarts
|
||||
*/
|
||||
private updateEChartsOption(): EChartsInstance {
|
||||
const { option, notMerge = false, lazyUpdate = false, showLoading, loadingOption = null } = this.props;
|
||||
// 1. get or initial the echarts object
|
||||
const echartInstance = this.getEchartsInstance();
|
||||
// 2. set the echarts option
|
||||
echartInstance.setOption(option, notMerge, lazyUpdate);
|
||||
// 3. set loading mask
|
||||
if (showLoading) echartInstance.showLoading(loadingOption);
|
||||
else echartInstance.hideLoading();
|
||||
|
||||
return echartInstance;
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const { style, className = '' } = this.props;
|
||||
// default height = 300
|
||||
const newStyle = { height: 300, ...style };
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(e: HTMLElement) => {
|
||||
this.ele = e;
|
||||
}}
|
||||
style={newStyle}
|
||||
className={`echarts-for-react ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
3
src/helper/is-equal.ts
Normal file
3
src/helper/is-equal.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import isEqual from 'fast-deep-equal';
|
||||
|
||||
export { isEqual };
|
||||
3
src/helper/is-function.ts
Normal file
3
src/helper/is-function.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function isFunction(v: any): boolean {
|
||||
return typeof v === 'function';
|
||||
}
|
||||
3
src/helper/is-string.ts
Normal file
3
src/helper/is-string.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function isString(v: any): boolean {
|
||||
return typeof v === 'string';
|
||||
}
|
||||
12
src/helper/pick.ts
Normal file
12
src/helper/pick.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 保留 object 中的部分内容
|
||||
* @param obj
|
||||
* @param keys
|
||||
*/
|
||||
export function pick(obj: Record<string, unknown>, keys: string[]): Record<string, unknown> {
|
||||
const r = {};
|
||||
keys.forEach((key) => {
|
||||
r[key] = obj[key];
|
||||
});
|
||||
return r;
|
||||
}
|
||||
15
src/index.ts
Normal file
15
src/index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as echarts from 'echarts';
|
||||
import { EChartsReactProps, EChartsOption, EChartsInstance } from './types';
|
||||
import EChartsReactCore from './core';
|
||||
|
||||
export { EChartsReactProps, EChartsOption, EChartsInstance };
|
||||
|
||||
// export the Component the echarts Object.
|
||||
export default class EChartsReact extends EChartsReactCore {
|
||||
constructor(props: EChartsReactProps) {
|
||||
super(props);
|
||||
|
||||
// 初始化为 echarts 整个包
|
||||
this.echarts = echarts;
|
||||
}
|
||||
}
|
||||
69
src/types.ts
Normal file
69
src/types.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export type EChartsOption = any;
|
||||
|
||||
export type EChartsInstance = any;
|
||||
|
||||
export type Opts = {
|
||||
readonly devicePixelRatio?: number;
|
||||
readonly renderer?: 'canvas' | 'svg';
|
||||
readonly width?: number | null | undefined | 'auto';
|
||||
readonly height?: number | null | undefined | 'auto';
|
||||
};
|
||||
|
||||
export type EChartsReactProps = {
|
||||
/**
|
||||
* echarts library entry, use it for import necessary.
|
||||
*/
|
||||
readonly echarts?: any;
|
||||
/**
|
||||
* `className` for container
|
||||
*/
|
||||
readonly className?: string;
|
||||
/**
|
||||
* `style` for container
|
||||
*/
|
||||
readonly style?: CSSProperties;
|
||||
/**
|
||||
* echarts option
|
||||
*/
|
||||
readonly option: EChartsOption;
|
||||
/**
|
||||
* echarts theme config, can be:
|
||||
* 1. theme name string
|
||||
* 2. theme object
|
||||
*/
|
||||
readonly theme?: string | Record<string, any>;
|
||||
/**
|
||||
* notMerge config for echarts, default is `false`
|
||||
*/
|
||||
readonly notMerge?: boolean;
|
||||
/**
|
||||
* lazyUpdate config for echarts, default is `false`
|
||||
*/
|
||||
readonly lazyUpdate?: boolean;
|
||||
/**
|
||||
* showLoading config for echarts, default is `false`
|
||||
*/
|
||||
readonly showLoading?: boolean;
|
||||
/**
|
||||
* loadingOption config for echarts, default is `null`
|
||||
*/
|
||||
readonly loadingOption?: any;
|
||||
/**
|
||||
* echarts opts config, default is `{}`
|
||||
*/
|
||||
readonly opts?: Opts;
|
||||
/**
|
||||
* when after chart reander, do the callback widht echarts instance
|
||||
*/
|
||||
readonly onChartReady?: (instance: EChartsInstance) => void;
|
||||
/**
|
||||
* bind events, default is `{}`
|
||||
*/
|
||||
readonly onEvents?: Record<string, Function>;
|
||||
/**
|
||||
* should update echarts options
|
||||
*/
|
||||
readonly shouldSetOption?: (prevProps: EChartsReactProps, props: EChartsReactProps) => boolean;
|
||||
};
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"target": "es5",
|
||||
"outDir": "lib",
|
||||
"declaration": true,
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
"types": ["jest", "react", "node"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user