├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .versionrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── images └── demo.png ├── package.json ├── packages ├── demo │ ├── .editorconfig │ ├── .gitignore │ ├── babel.config.js │ ├── config │ │ ├── dev.js │ │ ├── index.js │ │ └── prod.js │ ├── package.json │ ├── project.config.json │ ├── project.tt.json │ ├── src │ │ ├── app.config.ts │ │ ├── app.less │ │ ├── app.ts │ │ ├── assets │ │ │ └── js │ │ │ │ └── echarts.js │ │ ├── index.html │ │ └── pages │ │ │ └── index │ │ │ ├── index.config.ts │ │ │ ├── index.less │ │ │ └── index.tsx │ ├── tsconfig.json │ └── types │ │ └── global.d.ts └── echarts │ ├── .babelrc │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── core │ │ ├── index.tsx │ │ ├── types.ts │ │ └── utils.ts │ ├── hooks │ │ ├── index.ts │ │ ├── useFirstMountState.ts │ │ ├── usePrevious.ts │ │ ├── useUnMount.ts │ │ └── useUpdateEffect.ts │ ├── index.ts │ └── weapp │ │ ├── wx-canvas.ts │ │ └── wx-touch.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── release.js └── update-pkg.js ├── tsconfig.json └── turbo.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | packages/**/*.md 2 | scripts/**/*.js 3 | scripts 4 | weapp 5 | echarts.js 6 | commitlint.config.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:prettier/recommended", 12 | "eslint-config-prettier" 13 | ], 14 | "settings": { 15 | "react": { 16 | "version": "detect" 17 | } 18 | }, 19 | "parserOptions": { 20 | "sourceType": "module", 21 | "ecmaVersion": 2021 22 | }, 23 | "plugins": ["react", "@typescript-eslint"], 24 | "parser": "@typescript-eslint/parser", 25 | "rules": { 26 | "@typescript-eslint/no-explicit-any": "off", 27 | "@typescript-eslint/no-var-requires": "off", 28 | "react/react-in-jsx-scope": "off", 29 | "react/self-closing-comp": ["error"], 30 | "react/jsx-uses-react": "off", 31 | "quotes": ["error", "single"], 32 | "jsx-quotes": ["error", "prefer-single"], 33 | "no-debugger": "error" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | /packages/*/node_modules 8 | /*/node_modules 9 | /packages/*/dist/* 10 | 11 | # testing 12 | coverage 13 | 14 | # next.js 15 | .next/ 16 | out/ 17 | build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # turbo 36 | .turbo 37 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm exec commitlint --config commitlint.config.js --edit "${1}" -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm exec lint-staged -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | git-checks=false 2 | registry=https://registry.npmmirror.com/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "bracketSameLine": false, 4 | "endOfLine": "lf", 5 | "semi": false, 6 | "singleQuote": true, 7 | "jsxSingleQuote": true, 8 | "tabWidth": 2, 9 | "trailingComma": "all" 10 | } 11 | -------------------------------------------------------------------------------- /.versionrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | releaseCommitMessageFormat: 'release: {{currentTag}}', 3 | types: [ 4 | { type: 'release', section: 'release version' }, 5 | { type: 'feat', section: ' Features' }, 6 | { type: 'feature', section: 'Features' }, 7 | { type: 'fix', section: 'Bug Fixes' }, 8 | { type: 'perf', section: 'Performance Improvements' }, 9 | { type: 'revert', section: 'Reverts' }, 10 | { type: 'docs', section: 'Documentation', hidden: true }, 11 | { type: 'style', section: 'Styles', hidden: true }, 12 | { type: 'chore', section: 'Miscellaneous Chores', hidden: true }, 13 | { type: 'refactor', section: 'Code Refactoring', hidden: true }, 14 | { type: 'test', section: 'Tests', hidden: true }, 15 | { type: 'build', section: 'Build System', hidden: true }, 16 | { type: 'ci', section: 'Continuous Integration', hidden: true }, 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.2.2](https://github.com/qiuweikangdev/taro-react-echarts/compare/taro-react-echarts-v1.2.0...taro-react-echarts-v1.2.2) (2023-02-16) 6 | 7 | ### Bug Fixes 8 | 9 | - **echarts:** dispose echarts instance [#18](https://github.com/qiuweikangdev/taro-react-echarts/issues/18) ([a469d3e](https://github.com/qiuweikangdev/taro-react-echarts/commit/a469d3eed8b996c677df77e2900c769faed0eb90)) 10 | - removeEventListener 报错问题 #issue-23 ([7653032](https://github.com/qiuweikangdev/taro-react-echarts/commit/76530327061e43b95c23b1a7392b1727d4969941)), closes [#issue-23](https://github.com/qiuweikangdev/taro-react-echarts/issues/issue-23) 11 | 12 | ### release version 13 | 14 | - 1.2.1 ([cdf94c1](https://github.com/qiuweikangdev/taro-react-echarts/commit/cdf94c190c9798272bdfebc54bec74d1490304f0)) 15 | 16 | ### [1.2.1](https://github.com/qiuweikangdev/taro-react-echarts/compare/taro-react-echarts-v1.2.0...taro-react-echarts-v1.2.1) (2022-12-08) 17 | 18 | ### Bug Fixes 19 | 20 | - **echarts:** dispose echarts instance [#18](https://github.com/qiuweikangdev/taro-react-echarts/issues/18) ([a469d3e](https://github.com/qiuweikangdev/taro-react-echarts/commit/a469d3eed8b996c677df77e2900c769faed0eb90)) 21 | 22 | ## [1.2.0](https://github.com/qiuweikangdev/taro-react-echarts/compare/taro-react-echarts-v1.1.5...taro-react-echarts-v1.2.0) (2022-12-07) 23 | 24 | ### Features 25 | 26 | - **echarts:** expose chartRef and canvasRef ([8920fe1](https://github.com/qiuweikangdev/taro-react-echarts/commit/8920fe1a958eca246ee216142c1eb5cbca409308)) 27 | 28 | ### [1.1.5](https://github.com/qiuweikangdev/taro-react-echarts/compare/taro-react-echarts-v1.1.4...taro-react-echarts-v1.1.5) (2022-10-02) 29 | 30 | ### Bug Fixes 31 | 32 | - **echarts:** remove Opts、InitEchart ([07cd0cd](https://github.com/qiuweikangdev/taro-react-echarts/commit/07cd0cde8b1d235435c46bb7385448369c5b8d3b)) 33 | 34 | ### Features 35 | 36 | - release.js => generate version/tags ([8be84cf](https://github.com/qiuweikangdev/taro-react-echarts/commit/8be84cf8fca5943ab00961dc0f9a3f1425d5ad90)) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 qiuweikangdev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # taro-react-echarts 2 | 3 | 基于Taro3、React的H5和微信小程序多端图表组件 4 | 5 | - 兼容H5、微信小程序 6 | 7 | - 开箱即用,快速开发图表,满足在移动端各种可视化需求 8 | 9 | - 支持自定义构建echarts 10 | 11 | ![](https://raw.githubusercontent.com/qiuweikangdev/taro-react-echarts/master/images/demo.png) 12 | 13 | # 安装 14 | 15 | ```bash 16 | npm install taro-react-echarts 17 | ``` 18 | 19 | # 导入组件 20 | 21 | ```js 22 | import Echarts from 'taro-react-echarts' 23 | ``` 24 | 25 | # 参数说明 26 | 27 | ## Echarts 28 | 29 | | 参数 | 描述 | 类型 | 必传 | 默认值 | 30 | | --------------- | ------------------------------------------------------------ | ------- | ---- | ----------------- | 31 | | 本身参数 | 参考[Canvas](https://taro-docs.jd.com/taro/docs/components/canvas/) 【微信小程序】 | | | | 32 | | `echarts` | echarts对象,可[自定义构建](https://echarts.apache.org/zh/builder.html) | echarts | 是 | | 33 | | `option` | 参考[setOption](https://echarts.apache.org/zh/option.html#title) | object | 是 | | 34 | | `className` | echarts类名 | string | 否 | `key` | 35 | | `style` | echarts样式对象 | object | 否 | {height: '300px'} | 36 | | `className` | echarts类名 | string | 否 | | 37 | | `theme` | echarts 的主题 | string | 否 | | 38 | | `notMerge` | 默认为true,不跟之前设置的option合并,保证每次渲染都是最新的option | boolean | 否 | true | 39 | | `lazyUpdate` | setOption 时,延迟更新数据 | boolean | 否 | false | 40 | | `showLoading` | 图表渲染时,是否显示加载状态 | boolean | 否 | | 41 | | `loadingOption` | 参考[loading配置项](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading) | object | 否 | | 42 | | `opts` | 参考[echarts. init](https://echarts.apache.org/zh/api.html#echarts.init) | string | 否 | | 43 | | `onEvents` | 绑定 echarts 事件 | object | 否 | | 44 | | `isPage` | 表示是否是顶层页面级别 【1、注意嵌套在Popup 、Dialog 、Picker等弹出层都不是页面级别,需要设置为false,否则可能会不显示 2、以及嵌套在Tabs标签页中如果出现显示不正常,可设置isPage为false, 因为都有可能不触发useReady】 | boolean | 否 | true | 45 | 46 | ## Events 47 | 48 | | 事件名 | 描述 | 类型 | 必传 | 默认值 | 49 | | -------------- | ------------------------------ | -------- | --- | --- | 50 | | `onChartReady` | 当图表准备好时,将使用 echarts 对象作为参数回调函数 | Function | 否 | | 51 | 52 | # 使用 53 | 54 | ```js 55 | import { useRef } from 'react' 56 | import Echarts, { EChartOption, EchartsHandle } from 'taro-react-echarts' 57 | import echarts from '@/assets/js/echarts.js' 58 | 59 | export default function Demo() { 60 | const echartsRef = useRef(null) 61 | const option: EChartOption = { 62 | legend: { 63 | top: 50, 64 | left: 'center', 65 | z: 100 66 | }, 67 | tooltip: { 68 | trigger: 'axis', 69 | show: true, 70 | confine: true 71 | }, 72 | xAxis: { 73 | type: 'category', 74 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 75 | }, 76 | yAxis: { 77 | type: 'value' 78 | }, 79 | series: [ 80 | { 81 | data: [150, 230, 224, 218, 135, 147, 260], 82 | type: 'line' 83 | } 84 | ] 85 | } 86 | 87 | return ( 88 | 93 | ); 94 | } 95 | ``` 96 | 97 | # 友情推荐 98 | 99 | | 项目 | 描述 | 100 | | ------------------------------------------------------------ | ------------------------------------------------ | 101 | | [taro-react-table](https://github.com/qiuweikangdev/taro-react-table) | 基于 taro3、react 的 H5 和微信小程序多端表格组件 | 102 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const components = fs 5 | .readdirSync(path.resolve(__dirname, 'packages'), { withFileTypes: true }) 6 | .filter((dirent) => dirent.isDirectory()) 7 | .map((dirent) => dirent.name) 8 | 9 | /** @type {import('cz-git').UserConfig} */ 10 | module.exports = { 11 | extends: ['@commitlint/config-conventional'], 12 | rules: { 13 | 'body-leading-blank': [1, 'always'], 14 | 'footer-leading-blank': [1, 'always'], 15 | 'header-max-length': [2, 'always', 72], 16 | 'scope-case': [2, 'always', 'lower-case'], 17 | 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], 18 | 'subject-empty': [2, 'never'], 19 | 'subject-full-stop': [2, 'never', '.'], 20 | 'type-case': [2, 'always', 'lower-case'], 21 | 'type-empty': [2, 'never'], 22 | 'type-enum': [ 23 | 2, 24 | 'always', 25 | ['chore', 'docs', 'feat', 'fix', 'test', 'refactor', 'revert', 'style', 'release'], 26 | ], 27 | }, 28 | prompt: { 29 | typesAppend: [{ value: 'release', name: 'release: release version' }], 30 | scopes: [...components], 31 | allowCustomIssuePrefixs: false, 32 | allowEmptyIssuePrefixs: false, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuweikangdev/taro-react-echarts/b9e36d361ed3795a2277cbfe18690bfdf1939349/images/demo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taro-react-echarts", 3 | "version": "0.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "clear": "rimraf packages/*/{dist,node_modules}", 10 | "dev:h5": "cross-env NODE_ENV=dev pnpm update:pkg && turbo run dev:h5 --filter=demo", 11 | "dev:weapp": "cross-env NODE_ENV=dev pnpm update:pkg && turbo run dev:weapp --filter=demo", 12 | "build": "cross-env NODE_ENV=prod pnpm update:pkg && turbo run build:echarts --filter=taro-react-echarts", 13 | "turbo:publish": "cross-env NODE_ENV=prod pnpm update:pkg && turbo run publish", 14 | "update:pkg": "node ./scripts/update-pkg.js", 15 | "format": "prettier --write \"**/*.{js,ts,tsx,md}\"", 16 | "lint": "eslint --fix \"**/*.{js,ts,tsx}\" ", 17 | "prepare": "husky install", 18 | "release": "node ./scripts/release.js", 19 | "commit": "git cz" 20 | }, 21 | "devDependencies": { 22 | "@commitlint/cli": "^17.1.2", 23 | "@commitlint/config-conventional": "^17.1.0", 24 | "commander": "^9.4.1", 25 | "commitizen": "^4.2.5", 26 | "cross-env": "^7.0.3", 27 | "cz-git": "^1.3.11", 28 | "eslint": "^8.23.1", 29 | "eslint-config-prettier": "^8.5.0", 30 | "eslint-plugin-import": "^2.12.0", 31 | "eslint-plugin-prettier": "^4.2.1", 32 | "eslint-plugin-react": "^7.8.2", 33 | "eslint-plugin-react-hooks": "^4.2.0", 34 | "husky": "^8.0.1", 35 | "lint-staged": "^13.0.3", 36 | "prettier": "latest", 37 | "rimraf": "^3.0.2", 38 | "standard-version": "^9.5.0", 39 | "turbo": "latest", 40 | "typescript": "^4.1.0" 41 | }, 42 | "lint-staged": { 43 | "*.{js,ts,jsx,tsx}": "eslint --fix", 44 | "*.{md,json}": "prettier --write" 45 | }, 46 | "config": { 47 | "commitizen": { 48 | "path": "node_modules/cz-git" 49 | } 50 | }, 51 | "engines": { 52 | "npm": ">=7.0.0", 53 | "node": ">=14.0.0" 54 | }, 55 | "packageManager": "pnpm@7.8.0" 56 | } 57 | -------------------------------------------------------------------------------- /packages/demo/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /packages/demo/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | deploy_versions/ 3 | .temp/ 4 | .rn_temp/ 5 | node_modules/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /packages/demo/babel.config.js: -------------------------------------------------------------------------------- 1 | // babel-preset-taro 更多选项和默认值: 2 | // https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md 3 | module.exports = { 4 | presets: [ 5 | [ 6 | 'taro', 7 | { 8 | framework: 'react', 9 | ts: true, 10 | }, 11 | ], 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /packages/demo/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"development"', 4 | }, 5 | defineConstants: {}, 6 | mini: {}, 7 | h5: { 8 | devServer: { 9 | host: '127.0.0.1', 10 | port: 8888, 11 | }, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/demo/config/index.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | projectName: '.\\packages\\demo', 3 | date: '2022-9-21', 4 | designWidth: 750, 5 | deviceRatio: { 6 | 640: 2.34 / 2, 7 | 750: 1, 8 | 828: 1.81 / 2, 9 | }, 10 | sourceRoot: 'src', 11 | outputRoot: 'dist', 12 | plugins: [], 13 | defineConstants: {}, 14 | copy: { 15 | patterns: [], 16 | options: {}, 17 | }, 18 | framework: 'react', 19 | compiler: { 20 | type: 'webpack5', 21 | prebundle: { 22 | enable: false, 23 | }, 24 | }, 25 | cache: { 26 | enable: false, // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache 27 | }, 28 | mini: { 29 | postcss: { 30 | pxtransform: { 31 | enable: true, 32 | config: {}, 33 | }, 34 | url: { 35 | enable: true, 36 | config: { 37 | limit: 1024, // 设定转换尺寸上限 38 | }, 39 | }, 40 | cssModules: { 41 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 42 | config: { 43 | namingPattern: 'module', // 转换模式,取值为 global/module 44 | generateScopedName: '[name]__[local]___[hash:base64:5]', 45 | }, 46 | }, 47 | }, 48 | }, 49 | h5: { 50 | publicPath: '/', 51 | staticDirectory: 'static', 52 | postcss: { 53 | autoprefixer: { 54 | enable: true, 55 | config: {}, 56 | }, 57 | cssModules: { 58 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 59 | config: { 60 | namingPattern: 'module', // 转换模式,取值为 global/module 61 | generateScopedName: '[name]__[local]___[hash:base64:5]', 62 | }, 63 | }, 64 | }, 65 | }, 66 | rn: { 67 | appName: 'taroDemo', 68 | postcss: { 69 | cssModules: { 70 | enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true 71 | }, 72 | }, 73 | }, 74 | } 75 | 76 | module.exports = function (merge) { 77 | if (process.env.NODE_ENV === 'development') { 78 | return merge({}, config, require('./dev')) 79 | } 80 | return merge({}, config, require('./prod')) 81 | } 82 | -------------------------------------------------------------------------------- /packages/demo/config/prod.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | NODE_ENV: '"production"', 4 | }, 5 | defineConstants: {}, 6 | mini: {}, 7 | h5: { 8 | /** 9 | * WebpackChain 插件配置 10 | * @docs https://github.com/neutrinojs/webpack-chain 11 | */ 12 | // webpackChain (chain) { 13 | // /** 14 | // * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。 15 | // * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer 16 | // */ 17 | // chain.plugin('analyzer') 18 | // .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, []) 19 | // /** 20 | // * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。 21 | // * @docs https://github.com/chrisvfritz/prerender-spa-plugin 22 | // */ 23 | // const path = require('path') 24 | // const Prerender = require('prerender-spa-plugin') 25 | // const staticDir = path.join(__dirname, '..', 'dist') 26 | // chain 27 | // .plugin('prerender') 28 | // .use(new Prerender({ 29 | // staticDir, 30 | // routes: [ '/pages/index/index' ], 31 | // postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') }) 32 | // })) 33 | // } 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /packages/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "demo", 6 | "templateInfo": { 7 | "name": "default", 8 | "typescript": true, 9 | "css": "less" 10 | }, 11 | "scripts": { 12 | "build:weapp": "taro build --type weapp", 13 | "build:swan": "taro build --type swan", 14 | "build:alipay": "taro build --type alipay", 15 | "build:tt": "taro build --type tt", 16 | "build:h5": "taro build --type h5", 17 | "build:rn": "taro build --type rn", 18 | "build:qq": "taro build --type qq", 19 | "build:jd": "taro build --type jd", 20 | "build:quickapp": "taro build --type quickapp", 21 | "dev:weapp": "npm run build:weapp -- --watch", 22 | "dev:swan": "npm run build:swan -- --watch", 23 | "dev:alipay": "npm run build:alipay -- --watch", 24 | "dev:tt": "npm run build:tt -- --watch", 25 | "dev:h5": "npm run build:h5 -- --watch", 26 | "dev:rn": "npm run build:rn -- --watch", 27 | "dev:qq": "npm run build:qq -- --watch", 28 | "dev:jd": "npm run build:jd -- --watch", 29 | "dev:quickapp": "npm run build:quickapp -- --watch" 30 | }, 31 | "browserslist": [ 32 | "last 3 versions", 33 | "Android >= 4.1", 34 | "ios >= 8" 35 | ], 36 | "author": "", 37 | "dependencies": { 38 | "@babel/runtime": "^7.7.7", 39 | "@tarojs/components": "3.5.5", 40 | "@tarojs/helper": "3.5.5", 41 | "@tarojs/plugin-framework-react": "3.5.5", 42 | "@tarojs/plugin-platform-alipay": "3.5.5", 43 | "@tarojs/plugin-platform-jd": "3.5.5", 44 | "@tarojs/plugin-platform-qq": "3.5.5", 45 | "@tarojs/plugin-platform-swan": "3.5.5", 46 | "@tarojs/plugin-platform-tt": "3.5.5", 47 | "@tarojs/plugin-platform-weapp": "3.5.5", 48 | "@tarojs/react": "3.5.5", 49 | "@tarojs/router": "3.5.5", 50 | "@tarojs/runtime": "3.5.5", 51 | "@tarojs/shared": "3.5.5", 52 | "@tarojs/taro": "3.5.5", 53 | "@tarojs/taro-h5": "3.5.5", 54 | "react": "^18.0.0", 55 | "react-dom": "^18.0.0", 56 | "taro-react-echarts": "workspace:*" 57 | }, 58 | "devDependencies": { 59 | "@babel/core": "^7.8.0", 60 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", 61 | "@tarojs/cli": "3.5.5", 62 | "@tarojs/webpack5-runner": "3.5.5", 63 | "@types/react": "^18.0.0", 64 | "@types/react-dom": "17.0.2", 65 | "@types/webpack-env": "^1.13.6", 66 | "@typescript-eslint/eslint-plugin": "^5.20.0", 67 | "@typescript-eslint/parser": "^5.20.0", 68 | "babel-preset-taro": "3.5.5", 69 | "eslint": "^8.12.0", 70 | "eslint-config-taro": "3.5.5", 71 | "eslint-plugin-import": "^2.12.0", 72 | "eslint-plugin-react": "^7.8.2", 73 | "eslint-plugin-react-hooks": "^4.2.0", 74 | "react-refresh": "^0.11.0", 75 | "stylelint": "^14.4.0", 76 | "typescript": "^4.1.0", 77 | "webpack": "5.69.0" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/demo/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./dist", 3 | "projectname": ".\\packages\\demo", 4 | "description": "demo", 5 | "appid": "touristappid", 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": false, 9 | "enhance": false, 10 | "compileHotReLoad": false, 11 | "postcss": false, 12 | "minified": false 13 | }, 14 | "compileType": "miniprogram" 15 | } 16 | -------------------------------------------------------------------------------- /packages/demo/project.tt.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "./", 3 | "projectname": ".\\packages\\demo", 4 | "appid": "testAppId", 5 | "setting": { 6 | "es6": false, 7 | "minified": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/demo/src/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | pages: ['pages/index/index'], 3 | window: { 4 | backgroundTextStyle: 'light', 5 | navigationBarBackgroundColor: '#fff', 6 | navigationBarTitleText: 'WeChat', 7 | navigationBarTextStyle: 'black', 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/demo/src/app.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuweikangdev/taro-react-echarts/b9e36d361ed3795a2277cbfe18690bfdf1939349/packages/demo/src/app.less -------------------------------------------------------------------------------- /packages/demo/src/app.ts: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren } from 'react' 2 | import './app.less' 3 | 4 | class App extends Component { 5 | componentDidMount() {} 6 | 7 | componentDidShow() {} 8 | 9 | componentDidHide() {} 10 | 11 | render() { 12 | // this.props.children 是将要会渲染的页面 13 | return this.props.children 14 | } 15 | } 16 | 17 | export default App 18 | -------------------------------------------------------------------------------- /packages/demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | .\\packages\\demo 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/demo/src/pages/index/index.config.ts: -------------------------------------------------------------------------------- 1 | export default definePageConfig({ 2 | navigationBarTitleText: '首页', 3 | }) 4 | -------------------------------------------------------------------------------- /packages/demo/src/pages/index/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuweikangdev/taro-react-echarts/b9e36d361ed3795a2277cbfe18690bfdf1939349/packages/demo/src/pages/index/index.less -------------------------------------------------------------------------------- /packages/demo/src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | import Echarts, { EChartOption, EchartsHandle } from 'taro-react-echarts' 3 | import echarts from '../../assets/js/echarts' 4 | import './index.less' 5 | 6 | export default function Demo() { 7 | const echartsRef = useRef(null) 8 | const option: EChartOption = { 9 | legend: { 10 | top: 50, 11 | left: 'center', 12 | z: 100, 13 | }, 14 | tooltip: { 15 | trigger: 'axis', 16 | show: true, 17 | confine: true, 18 | }, 19 | xAxis: { 20 | type: 'category', 21 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 22 | }, 23 | yAxis: { 24 | type: 'value', 25 | }, 26 | series: [ 27 | { 28 | data: [150, 230, 224, 218, 135, 147, 260], 29 | type: 'line', 30 | }, 31 | ], 32 | } 33 | 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /packages/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "removeComments": false, 6 | "preserveConstEnums": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "outDir": "lib", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "strictNullChecks": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "rootDir": ".", 18 | "jsx": "react-jsx", 19 | "allowJs": true, 20 | "resolveJsonModule": true, 21 | "typeRoots": ["node_modules/@types"] 22 | }, 23 | "include": ["./src", "./types"], 24 | "compileOnSave": false 25 | } 26 | -------------------------------------------------------------------------------- /packages/demo/types/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.png' 4 | declare module '*.gif' 5 | declare module '*.jpg' 6 | declare module '*.jpeg' 7 | declare module '*.svg' 8 | declare module '*.css' 9 | declare module '*.less' 10 | declare module '*.scss' 11 | declare module '*.sass' 12 | declare module '*.styl' 13 | 14 | declare namespace NodeJS { 15 | interface ProcessEnv { 16 | TARO_ENV: 'weapp' | 'swan' | 'alipay' | 'h5' | 'rn' | 'tt' | 'quickapp' | 'qq' | 'jd' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/echarts/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ] 10 | } -------------------------------------------------------------------------------- /packages/echarts/README.md: -------------------------------------------------------------------------------- 1 | # taro-react-echarts 2 | 3 | 基于Taro3、React的H5和微信小程序多端图表组件 4 | 5 | - 兼容H5、微信小程序 6 | 7 | - 开箱即用,快速开发图表,满足在移动端各种可视化需求 8 | 9 | - 支持自定义构建echarts 10 | 11 | ![](https://raw.githubusercontent.com/qiuweikangdev/taro-react-echarts/master/images/demo.png) 12 | 13 | # 安装 14 | 15 | ```bash 16 | npm install taro-react-echarts 17 | ``` 18 | 19 | # 导入组件 20 | 21 | ```js 22 | import Echarts from 'taro-react-echarts' 23 | ``` 24 | 25 | # 参数说明 26 | 27 | ## Echarts 28 | 29 | | 参数 | 描述 | 类型 | 必传 | 默认值 | 30 | | --------------- | ------------------------------------------------------------ | ------- | ---- | ----------------- | 31 | | 本身参数 | 参考[Canvas](https://taro-docs.jd.com/taro/docs/components/canvas/) 【微信小程序】 | | | | 32 | | `echarts` | echarts对象,可[自定义构建](https://echarts.apache.org/zh/builder.html) | echarts | 是 | | 33 | | `option` | 参考[setOption](https://echarts.apache.org/zh/option.html#title) | object | 是 | | 34 | | `className` | echarts类名 | string | 否 | `key` | 35 | | `style` | echarts样式对象 | object | 否 | {height: '300px'} | 36 | | `className` | echarts类名 | string | 否 | | 37 | | `theme` | echarts 的主题 | string | 否 | | 38 | | `notMerge` | 默认为true,不跟之前设置的option合并,保证每次渲染都是最新的option | boolean | 否 | true | 39 | | `lazyUpdate` | setOption 时,延迟更新数据 | boolean | 否 | false | 40 | | `showLoading` | 图表渲染时,是否显示加载状态 | boolean | 否 | | 41 | | `loadingOption` | 参考[loading配置项](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading) | object | 否 | | 42 | | `opts` | 参考[echarts. init](https://echarts.apache.org/zh/api.html#echarts.init) | string | 否 | | 43 | | `onEvents` | 绑定 echarts 事件 | object | 否 | | 44 | | `isPage` | 表示是否是顶层页面级别 【1、注意嵌套在Popup 、Dialog 、Picker等弹出层都不是页面级别,需要设置为false,否则可能会不显示 2、以及嵌套在Tabs标签页中如果出现显示不正常,可设置isPage为false, 因为都有可能不触发useReady】 | boolean | 否 | true | 45 | 46 | ## Events 47 | 48 | | 事件名 | 描述 | 类型 | 必传 | 默认值 | 49 | | -------------- | --------------------------------------------------- | -------- | ---- | ------ | 50 | | `onChartReady` | 当图表准备好时,将使用 echarts 对象作为参数回调函数 | Function | 否 | | 51 | 52 | # 使用 53 | 54 | ```js 55 | import { useRef } from 'react' 56 | import Echarts, { EChartOption, EchartsHandle } from 'taro-react-echarts' 57 | import echarts from '@/assets/js/echarts.js' 58 | 59 | export default function Demo() { 60 | const echartsRef = useRef(null) 61 | const option: EChartOption = { 62 | legend: { 63 | top: 50, 64 | left: 'center', 65 | z: 100 66 | }, 67 | tooltip: { 68 | trigger: 'axis', 69 | show: true, 70 | confine: true 71 | }, 72 | xAxis: { 73 | type: 'category', 74 | data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] 75 | }, 76 | yAxis: { 77 | type: 'value' 78 | }, 79 | series: [ 80 | { 81 | data: [150, 230, 224, 218, 135, 147, 260], 82 | type: 'line' 83 | } 84 | ] 85 | } 86 | 87 | return ( 88 | 93 | ); 94 | } 95 | ``` 96 | 97 | # 友情推荐 98 | 99 | | 项目 | 描述 | 100 | | ------------------------------------------------------------ | ------------------------------------------------ | 101 | | [taro-react-table](https://github.com/qiuweikangdev/taro-react-table) | 基于 taro3、react 的 H5 和微信小程序多端表格组件 | 102 | 103 | -------------------------------------------------------------------------------- /packages/echarts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taro-react-echarts", 3 | "version": "1.2.2", 4 | "description": "taro图表", 5 | "scripts": { 6 | "build:echarts": "rimraf dist && rollup --config ./rollup.config.js", 7 | "publish:echarts": "pnpm publish --filter=taro-react-echarts" 8 | }, 9 | "keywords": [ 10 | "taro", 11 | "react", 12 | "echarts", 13 | "mobile", 14 | "component", 15 | "taro-echarts", 16 | "taro-react-echarts" 17 | ], 18 | "author": "qiuweikang", 19 | "license": "MIT", 20 | "main": "dist/index.js", 21 | "module": "dist/index.esm.js", 22 | "publishConfig": { 23 | "registry": "https://registry.npmjs.org", 24 | "main": "dist/index.js", 25 | "module": "dist/index.esm.js", 26 | "types": "dist/index.d.ts" 27 | }, 28 | "files": [ 29 | "dist", 30 | "package.json", 31 | "README.md", 32 | "LICENSE" 33 | ], 34 | "browserslist": [ 35 | "last 3 versions", 36 | "Android >= 4.1", 37 | "ios >= 8" 38 | ], 39 | "devDependencies": { 40 | "@babel/core": "^7.8.0", 41 | "@babel/preset-env": "^7.19.1", 42 | "@rollup/plugin-alias": "^3.1.9", 43 | "@rollup/plugin-babel": "^5.3.1", 44 | "@rollup/plugin-commonjs": "^22.0.2", 45 | "@rollup/plugin-node-resolve": "^14.1.0", 46 | "@rollup/plugin-typescript": "^8.5.0", 47 | "@tarojs/cli": "3.5.5", 48 | "@tarojs/components": "3.5.5", 49 | "@tarojs/plugin-framework-react": "3.5.5", 50 | "@tarojs/plugin-platform-weapp": "3.5.5", 51 | "@tarojs/react": "3.5.5", 52 | "@tarojs/runtime": "3.5.5", 53 | "@tarojs/shared": "3.5.5", 54 | "@tarojs/taro": "3.5.5", 55 | "@tarojs/taro-h5": "3.5.5", 56 | "@types/echarts": "^4.9.16", 57 | "@types/react": "^18.0.0", 58 | "@types/react-dom": "17.0.2", 59 | "@types/webpack-env": "^1.13.6", 60 | "babel-preset-taro": "3.5.5", 61 | "react": "^18.0.0", 62 | "react-dom": "^18.0.0", 63 | "rollup": "^2.79.0", 64 | "typescript": "^4.1.0" 65 | }, 66 | "peerDependencies": { 67 | "@tarojs/components": ">=3", 68 | "@tarojs/taro": ">=3", 69 | "react": ">=16.9", 70 | "react-dom": ">=16.9" 71 | }, 72 | "bugs": { 73 | "url": "https://github.com/qiuweikangdev/taro-react-echarts/issues" 74 | }, 75 | "homepage": "https://github.com/qiuweikangdev/taro-react-echarts#readme" 76 | } 77 | -------------------------------------------------------------------------------- /packages/echarts/rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import RollupTypescript from '@rollup/plugin-typescript' 3 | import RollupBabel from '@rollup/plugin-babel' 4 | import { nodeResolve } from '@rollup/plugin-node-resolve' 5 | import RollupCommonjs from '@rollup/plugin-commonjs' 6 | import pkg from './package.json' 7 | 8 | const resolveFile = (source) => path.resolve(__dirname, source) 9 | 10 | const externalPackages = [ 11 | 'react', 12 | 'react-dom', 13 | '@tarojs/components', 14 | '@tarojs/runtime', 15 | '@tarojs/taro', 16 | '@tarojs/react', 17 | ] 18 | export default { 19 | input: resolveFile('src/index.ts'), 20 | output: [ 21 | { 22 | file: resolveFile(pkg.publishConfig.main), 23 | format: 'cjs', 24 | sourcemap: true, 25 | exports: 'auto', 26 | }, 27 | { 28 | file: resolveFile(pkg.publishConfig.module), 29 | format: 'es', 30 | sourcemap: true, 31 | exports: 'auto', 32 | }, 33 | ], 34 | external: externalPackages, 35 | plugins: [ 36 | nodeResolve(), 37 | RollupCommonjs(), 38 | RollupBabel({ 39 | exclude: 'node_modules/**', 40 | }), 41 | RollupTypescript(), 42 | ], 43 | } 44 | -------------------------------------------------------------------------------- /packages/echarts/src/core/index.tsx: -------------------------------------------------------------------------------- 1 | import Taro, { nextTick, useReady } from '@tarojs/taro' 2 | import { Canvas, View } from '@tarojs/components' 3 | import { 4 | useRef, 5 | useState, 6 | useMemo, 7 | memo, 8 | useEffect, 9 | CSSProperties, 10 | ForwardRefRenderFunction, 11 | forwardRef, 12 | useImperativeHandle, 13 | } from 'react' 14 | import { isString, isFunction, isEqual, pick, uniqueId, compareVersion, tripleDefer } from './utils' 15 | import WxCanvas from '../weapp/wx-canvas' 16 | import { touchEnd, touchMove, touchStart } from '../weapp/wx-touch' 17 | import { usePrevious, useUnMount, useUpdateEffect } from '../hooks' 18 | import { EChartsProps, InitEchart, EChartsInstance, EchartsHandle } from './types' 19 | 20 | const Echarts: ForwardRefRenderFunction = ( 21 | { echarts, isPage = true, canvasId: pCanvasId, ...props }, 22 | ref, 23 | ) => { 24 | const canvasRef = useRef() 25 | const chartRef = useRef() 26 | const [isInitialResize, setIsInitialResize] = useState(true) 27 | const prevProps = usePrevious(props) 28 | const canvasId = useMemo(() => pCanvasId || uniqueId('canvas_'), [pCanvasId]) 29 | const canvasProps = useMemo( 30 | () => [ 31 | 'disableScroll', 32 | 'disableScroll', 33 | 'onTouchCancel', 34 | 'onLongTap', 35 | 'onError', 36 | 'nativeProps', 37 | 'className', 38 | 'key', 39 | 'hidden', 40 | 'animation', 41 | ], 42 | [], 43 | ) 44 | const canvasStyle = useMemo( 45 | () => 46 | ({ 47 | width: '100%', 48 | height: '300px', 49 | ...(props.style as CSSProperties), 50 | } as CSSProperties), 51 | [props.style], 52 | ) 53 | /** 54 | * issues: https://github.com/NervJS/taro/issues/7116 55 | * 获取小程序渲染层的节点要在 onReady 生命周期,等同于 useReady hooks 56 | * 访问小程序渲染层的 DOM 节点。 57 | */ 58 | useReady(() => { 59 | // 顶层页面级别才触发useReady 【注意Popup 、Dialog 等弹出层 都不是页面级别】 60 | if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP && isPage) { 61 | nextTick(() => { 62 | initChart() 63 | }) 64 | } 65 | }) 66 | 67 | useEffect(() => { 68 | if (Taro.getEnv() === Taro.ENV_TYPE.WEB || !isPage) { 69 | tripleDefer(() => { 70 | nextTick(() => { 71 | initChart() 72 | }) 73 | }) 74 | } 75 | }, []) 76 | 77 | useUnMount(() => { 78 | dispose() 79 | }) 80 | 81 | useUpdateEffect(() => { 82 | if ( 83 | !isEqual(prevProps?.theme, props.theme) || 84 | !isEqual(prevProps?.opts, props.opts) || 85 | !isEqual(prevProps?.onEvents, props.onEvents) 86 | ) { 87 | dispose() 88 | initChart() // re-render 89 | return 90 | } 91 | 92 | // update 93 | const pickKeys = ['option', 'notMerge', 'lazyUpdate', 'showLoading', 'loadingOption'] 94 | if (!isEqual(pick(props, pickKeys), pick(prevProps, pickKeys))) { 95 | updateEChartsOption() 96 | } 97 | 98 | /** 99 | * resize: style 、className 100 | */ 101 | if ( 102 | !isEqual(prevProps?.style, props.style) || 103 | !isEqual(prevProps?.className, props.className) 104 | ) { 105 | resize(canvasRef.current) 106 | } 107 | }, [props]) 108 | 109 | // 大小变化 110 | const resize = (canvas) => { 111 | const echartsInstance = echarts.getInstanceByDom(canvas) 112 | // 调整大小不应在第一次渲染时发生,因为它会取消初始 echarts 动画 113 | if (!isInitialResize) { 114 | try { 115 | echartsInstance.resize({ 116 | width: 'auto', 117 | height: 'auto', 118 | }) 119 | } catch (e) { 120 | console.warn(e) 121 | } 122 | } 123 | setIsInitialResize(false) 124 | } 125 | 126 | const initEchartsInstance = async ({ width, height, devicePixelRatio }: InitEchart) => { 127 | const { theme, opts } = props 128 | return new Promise((resolve, reject) => { 129 | if (canvasRef.current) { 130 | chartRef.current = echarts.init(canvasRef.current, theme, { 131 | width, 132 | height, 133 | devicePixelRatio, 134 | ...opts, 135 | }) 136 | if (Taro.getEnv() === Taro.ENV_TYPE.WEB) { 137 | /** 138 | * echart同一个dom下多次动态渲染值,防止值、事件重复互相影响 139 | * 每次init之后,先dispose释放下资源,再重新init 140 | */ 141 | const echartsInstance = echarts.getInstanceByDom(canvasRef.current) 142 | echartsInstance.on('finished', () => { 143 | echarts.dispose(canvasRef.current) 144 | // 获取渲染后的width、height 145 | const newOpts = { 146 | width, 147 | height, 148 | devicePixelRatio, 149 | ...opts, 150 | } 151 | chartRef.current = echarts.init(canvasRef.current, theme, newOpts) 152 | resolve(chartRef.current) 153 | }) 154 | } else { 155 | resolve(chartRef.current) 156 | } 157 | } else { 158 | reject(null) 159 | } 160 | }) 161 | } 162 | 163 | const updateEChartsOption = () => { 164 | /** 165 | * 官方文档:https://echarts.apache.org/zh/api.html#echartsInstance.setOption 166 | */ 167 | const { 168 | option, 169 | notMerge = true, // 不跟之前设置的option合并,保证每次渲染都是最新的option 170 | lazyUpdate = false, // 设置完 option 后是否不立即更新图表,默认为 false,即同步立即更新。如果为 true,则会在下一个 animation frame 中,才更新图表 171 | showLoading, 172 | loadingOption = null, 173 | } = props 174 | // 1. 获取echarts实例 175 | const echartInstance = echarts.getInstanceByDom(canvasRef.current) 176 | if (echartInstance) { 177 | // 2. 设置option 178 | echartInstance.setOption(option, notMerge, lazyUpdate) 179 | // 3. 显示加载动画效果 180 | if (showLoading) echartInstance.showLoading(loadingOption) 181 | else echartInstance.hideLoading() 182 | } 183 | 184 | return echartInstance 185 | } 186 | 187 | // 绑定事件 188 | const bindEvents = (instance, events) => { 189 | function _bindEvent(eventName, func) { 190 | if (isString(eventName) && isFunction(func)) { 191 | instance.on(eventName, (param) => { 192 | func(param, instance) 193 | }) 194 | } 195 | } 196 | 197 | for (const eventName in events) { 198 | if (Object.prototype.hasOwnProperty.call(events, eventName)) { 199 | _bindEvent(eventName, events[eventName]) 200 | } 201 | } 202 | } 203 | 204 | // 渲染图表 205 | const renderEcharts = async ({ width, height, devicePixelRatio }: InitEchart) => { 206 | const { onEvents, onChartReady } = props 207 | // 1. 初始化图表 208 | await initEchartsInstance({ 209 | width, 210 | height, 211 | devicePixelRatio, 212 | }) 213 | // 2. 更新echarts实例 214 | const echartsInstance = updateEChartsOption() 215 | // 3. 绑定事件 216 | bindEvents(echartsInstance, onEvents || {}) 217 | // 4. 图表渲染完成 218 | if (isFunction(onChartReady)) onChartReady?.(echartsInstance) 219 | 220 | // 5. resize 221 | if (canvasRef.current) { 222 | resize(canvasRef.current) 223 | } 224 | } 225 | 226 | // 销毁echarts实例 227 | const dispose = () => { 228 | if (chartRef.current) { 229 | echarts?.dispose(chartRef.current) 230 | } 231 | } 232 | 233 | // 初始化微信小程序图表 234 | const initWexinChart = () => { 235 | const query = Taro.createSelectorQuery() 236 | query 237 | .select(`#${canvasId}`) 238 | .fields({ 239 | node: true, 240 | size: true, 241 | }) 242 | .exec((res) => { 243 | const [result] = res 244 | if (result) { 245 | const { node, width, height } = result || {} 246 | const canvasNode = node 247 | const canvasDpr = Taro.getSystemInfoSync().pixelRatio 248 | const ctx = canvasNode.getContext('2d') 249 | const canvas = new WxCanvas(ctx, true, canvasNode) 250 | echarts?.setCanvasCreator(() => { 251 | return canvas 252 | }) 253 | canvasRef.current = canvas as any 254 | renderEcharts({ 255 | width, 256 | height, 257 | devicePixelRatio: canvasDpr, 258 | }) 259 | } 260 | }) 261 | } 262 | 263 | // 初始化图表 264 | const initChart = () => { 265 | if (Taro.getEnv() === Taro.ENV_TYPE.WEB && canvasRef.current) { 266 | const width = props.style?.width || canvasRef.current?.clientWidth || window.innerWidth 267 | const height = props.style?.height || canvasRef.current?.clientHeight || 300 268 | renderEcharts({ 269 | width, 270 | height, 271 | devicePixelRatio: window.devicePixelRatio, 272 | }) 273 | } else { 274 | const version = Taro.getSystemInfoSync().SDKVersion 275 | const canUseNewCanvas = compareVersion(version, '2.9.0') >= 0 276 | if (canUseNewCanvas) { 277 | // console.log('微信基础库版本大于2.9.0,开始使用'); 278 | // 2.9.0 可以使用 279 | initWexinChart() 280 | } else { 281 | console.error(`当前基础库为${version}, 微信基础库版本过低,需大于等于 2.9.0`) 282 | } 283 | } 284 | } 285 | 286 | useImperativeHandle(ref, () => ({ chartRef, canvasRef })) 287 | 288 | // container component 289 | const renderContainerComponent = useMemo(() => { 290 | if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP) { 291 | return ( 292 | touchStart({ chart: chartRef.current, event })} 299 | onTouchMove={(event) => touchMove({ chart: chartRef.current, event })} 300 | onTouchEnd={(event) => touchEnd({ chart: chartRef.current, event })} 301 | {...pick(props, canvasProps)} 302 | /> 303 | ) 304 | } 305 | return 306 | }, [props, canvasProps, canvasId]) 307 | 308 | return renderContainerComponent 309 | } 310 | 311 | export default memo(forwardRef(Echarts)) 312 | -------------------------------------------------------------------------------- /packages/echarts/src/core/types.ts: -------------------------------------------------------------------------------- 1 | import { CanvasProps } from '@tarojs/components/types/Canvas' 2 | import { EChartOption, EChartsLoadingOption, ECharts } from 'echarts' 3 | import { CSSProperties, MutableRefObject } from 'react' 4 | export type { EChartOption, ECharts as EChartsInstance, EChartsLoadingOption } from 'echarts' 5 | 6 | export type Opts = { 7 | devicePixelRatio?: number | undefined 8 | renderer?: string | undefined 9 | width?: number | string | undefined 10 | height?: number | string | undefined 11 | } 12 | 13 | export type EChartsProps = CanvasProps & { 14 | echarts: any 15 | className?: string 16 | style?: CSSProperties 17 | option: EChartOption 18 | theme?: string | Record 19 | notMerge?: boolean 20 | lazyUpdate?: boolean 21 | showLoading?: boolean 22 | loadingOption?: EChartsLoadingOption 23 | /** 24 | * https://echarts.apache.org/zh/api.html#echarts.init 25 | */ 26 | opts?: Opts 27 | onChartReady?: (instance: ECharts) => void 28 | onEvents?: Record void> 29 | isPage?: boolean 30 | } 31 | 32 | export type InitEchart = { 33 | devicePixelRatio: number | undefined 34 | width: number | string | undefined 35 | height: number | string | undefined 36 | } 37 | 38 | export type EchartsHandle = { 39 | canvasRef: Partial> 40 | chartRef: Partial> 41 | } 42 | -------------------------------------------------------------------------------- /packages/echarts/src/core/utils.ts: -------------------------------------------------------------------------------- 1 | export function isString(v) { 2 | return typeof v === 'string' 3 | } 4 | 5 | export function isFunction(v) { 6 | return typeof v === 'function' 7 | } 8 | 9 | export function isEqual(x, y) { 10 | if (x === y) { 11 | return true 12 | } else if (typeof x === 'object' && x !== null && typeof y === 'object' && y !== null) { 13 | const keysX = Object.keys(x) 14 | const keysY = Object.keys(y) 15 | if (keysX.length !== keysY.length) { 16 | return false 17 | } 18 | for (const key of keysX) { 19 | if (!isEqual(x[key], y[key])) { 20 | return false 21 | } 22 | } 23 | return true 24 | } else { 25 | return false 26 | } 27 | } 28 | 29 | export function pick(obj = {}, keys) { 30 | const r = {} 31 | keys.forEach((key) => { 32 | r[key] = obj[key] 33 | }) 34 | return r 35 | } 36 | 37 | const idCounter = {} 38 | export function uniqueId(prefix = '$unique$') { 39 | if (!idCounter[prefix]) { 40 | idCounter[prefix] = 0 41 | } 42 | 43 | const id = ++idCounter[prefix] 44 | if (prefix === '$unique$') { 45 | return `${id}` 46 | } 47 | 48 | return `${prefix}${id}` 49 | } 50 | 51 | export function compareVersion(v1, v2) { 52 | v1 = v1.split('.') 53 | v2 = v2.split('.') 54 | const len = Math.max(v1.length, v2.length) 55 | 56 | while (v1.length < len) { 57 | v1.push('0') 58 | } 59 | while (v2.length < len) { 60 | v2.push('0') 61 | } 62 | 63 | for (let i = 0; i < len; i++) { 64 | const num1 = parseInt(v1[i]) 65 | const num2 = parseInt(v2[i]) 66 | 67 | if (num1 > num2) { 68 | return 1 69 | } else if (num1 < num2) { 70 | return -1 71 | } 72 | } 73 | return 0 74 | } 75 | 76 | export function tripleDefer(func: () => void): void { 77 | setTimeout(() => setTimeout(() => setTimeout(() => func()))) 78 | } 79 | -------------------------------------------------------------------------------- /packages/echarts/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import usePrevious from './usePrevious' 2 | import useUnMount from './useUnMount' 3 | import useFirstMountState from './useFirstMountState' 4 | import useUpdateEffect from './useUpdateEffect' 5 | 6 | export { usePrevious, useUnMount, useFirstMountState, useUpdateEffect } 7 | -------------------------------------------------------------------------------- /packages/echarts/src/hooks/useFirstMountState.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | export default function useFirstMountState(): boolean { 4 | const isFirst = useRef(true) 5 | 6 | if (isFirst.current) { 7 | isFirst.current = false 8 | 9 | return true 10 | } 11 | 12 | return isFirst.current 13 | } 14 | -------------------------------------------------------------------------------- /packages/echarts/src/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export default function usePrevious(value) { 4 | const ref = useRef() 5 | useEffect(() => { 6 | ref.current = value 7 | }) 8 | return ref.current 9 | } 10 | -------------------------------------------------------------------------------- /packages/echarts/src/hooks/useUnMount.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | export default function useUnMount(fn: () => void) { 4 | useEffect(() => fn, []) 5 | } 6 | -------------------------------------------------------------------------------- /packages/echarts/src/hooks/useUpdateEffect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import useFirstMountState from './useFirstMountState' 3 | 4 | export default function useUpdateEffect(effect, deps) { 5 | const isFirstMount = useFirstMountState() // 是否首次渲染 6 | 7 | useEffect(() => { 8 | if (!isFirstMount) { 9 | // 如果不是首次,则执行 effect 函数 10 | return effect() 11 | } 12 | }, deps) 13 | } 14 | -------------------------------------------------------------------------------- /packages/echarts/src/index.ts: -------------------------------------------------------------------------------- 1 | import Echarts from './core' 2 | 3 | export type { 4 | EChartsProps, 5 | EChartOption, 6 | EChartsInstance, 7 | EChartsLoadingOption, 8 | EchartsHandle, 9 | } from './core/types' 10 | 11 | export default Echarts 12 | -------------------------------------------------------------------------------- /packages/echarts/src/weapp/wx-canvas.ts: -------------------------------------------------------------------------------- 1 | import { CanvasContext } from '@tarojs/taro' 2 | 3 | export default class WxCanvas { 4 | private ctx: CanvasContext 5 | private chart: any 6 | private canvasNode: any 7 | private event: {} 8 | 9 | constructor(ctx: CanvasContext, isNew: boolean, canvasNode?: any) { 10 | this.ctx = ctx 11 | this.chart = null 12 | if (isNew) { 13 | this.canvasNode = canvasNode 14 | } else { 15 | this._initStyle(ctx) 16 | } 17 | 18 | this._initEvent() 19 | } 20 | 21 | getContext(contextType) { 22 | if (contextType === '2d') { 23 | return this.ctx 24 | } 25 | } 26 | 27 | setChart(chart) { 28 | this.chart = chart 29 | } 30 | 31 | attachEvent() { 32 | // noop 33 | } 34 | 35 | addEventListener() { 36 | // noop 37 | } 38 | 39 | removeEventListener() { 40 | // noop 41 | } 42 | 43 | detachEvent() { 44 | // noop 45 | } 46 | 47 | _initCanvas(zrender, ctx) { 48 | zrender.util.getContext = function () { 49 | return ctx 50 | } 51 | 52 | zrender.util.$override('measureText', function (text, font) { 53 | ctx.font = font || '12px sans-serif' 54 | return ctx.measureText(text) 55 | }) 56 | } 57 | 58 | _initStyle(ctx) { 59 | var styles = [ 60 | 'fillStyle', 61 | 'strokeStyle', 62 | 'globalAlpha', 63 | 'textAlign', 64 | 'textBaseAlign', 65 | 'shadow', 66 | 'lineWidth', 67 | 'lineCap', 68 | 'lineJoin', 69 | 'lineDash', 70 | 'miterLimit', 71 | 'fontSize', 72 | ] 73 | 74 | styles.forEach((style) => { 75 | Object.defineProperty(ctx, style, { 76 | set: (value) => { 77 | if ( 78 | (style !== 'fillStyle' && style !== 'strokeStyle') || 79 | (value !== 'none' && value !== null) 80 | ) { 81 | ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value) 82 | } 83 | }, 84 | }) 85 | }) 86 | 87 | ctx.createRadialGradient = function () { 88 | return ctx.createCircularGradient.apply(ctx, arguments) 89 | } 90 | } 91 | 92 | _initEvent() { 93 | this.event = {} 94 | const eventNames = [ 95 | { 96 | wxName: 'touchStart', 97 | ecName: 'mousedown', 98 | }, 99 | { 100 | wxName: 'touchMove', 101 | ecName: 'mousemove', 102 | }, 103 | { 104 | wxName: 'touchEnd', 105 | ecName: 'mouseup', 106 | }, 107 | { 108 | wxName: 'touchEnd', 109 | ecName: 'click', 110 | }, 111 | ] 112 | 113 | eventNames.forEach((name) => { 114 | this.event[name.wxName] = (e) => { 115 | const touch = e.touches[0] 116 | this.chart.getZr().handler.dispatch(name.ecName, { 117 | zrX: name.wxName === 'tap' ? touch.clientX : touch.x, 118 | zrY: name.wxName === 'tap' ? touch.clientY : touch.y, 119 | }) 120 | } 121 | }) 122 | } 123 | 124 | set width(w) { 125 | if (this.canvasNode) this.canvasNode.width = w 126 | } 127 | set height(h) { 128 | if (this.canvasNode) this.canvasNode.height = h 129 | } 130 | 131 | get width() { 132 | if (this.canvasNode) return this.canvasNode.width 133 | return 0 134 | } 135 | get height() { 136 | if (this.canvasNode) return this.canvasNode.height 137 | return 0 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /packages/echarts/src/weapp/wx-touch.ts: -------------------------------------------------------------------------------- 1 | export function wrapTouch(event) { 2 | for (let i = 0; i < event.touches.length; ++i) { 3 | const touch = event.touches[i] 4 | touch.offsetX = touch.x 5 | touch.offsetY = touch.y 6 | } 7 | return event 8 | } 9 | 10 | export function touchStart({ chart, event }) { 11 | if (chart && event.touches.length > 0) { 12 | const touch = event.touches[0] 13 | const handler = chart.getZr().handler 14 | handler.dispatch('mousedown', { 15 | zrX: touch.x, 16 | zrY: touch.y, 17 | preventDefault: () => {}, 18 | stopImmediatePropagation: () => {}, 19 | stopPropagation: () => {}, 20 | }) 21 | handler.dispatch('mousemove', { 22 | zrX: touch.x, 23 | zrY: touch.y, 24 | preventDefault: () => {}, 25 | stopImmediatePropagation: () => {}, 26 | stopPropagation: () => {}, 27 | }) 28 | handler.processGesture(wrapTouch(event), 'start') 29 | } 30 | } 31 | 32 | export function touchMove({ chart, event }) { 33 | if (chart && event.touches.length > 0) { 34 | const touch = event.touches[0] 35 | const handler = chart.getZr().handler 36 | handler.dispatch('mousedown', { 37 | zrX: touch.x, 38 | zrY: touch.y, 39 | preventDefault: () => {}, 40 | stopImmediatePropagation: () => {}, 41 | stopPropagation: () => {}, 42 | }) 43 | handler.dispatch('mousemove', { 44 | zrX: touch.x, 45 | zrY: touch.y, 46 | preventDefault: () => {}, 47 | stopImmediatePropagation: () => {}, 48 | stopPropagation: () => {}, 49 | }) 50 | handler.processGesture(wrapTouch(event), 'start') 51 | } 52 | } 53 | 54 | export function touchEnd({ chart, event }) { 55 | if (chart) { 56 | const touch = event.changedTouches ? event.changedTouches[0] : {} 57 | const handler = chart.getZr().handler 58 | handler.dispatch('mouseup', { 59 | zrX: touch.x, 60 | zrY: touch.y, 61 | preventDefault: () => {}, 62 | stopImmediatePropagation: () => {}, 63 | stopPropagation: () => {}, 64 | }) 65 | handler.dispatch('click', { 66 | zrX: touch.x, 67 | zrY: touch.y, 68 | preventDefault: () => {}, 69 | stopImmediatePropagation: () => {}, 70 | stopPropagation: () => {}, 71 | }) 72 | handler.processGesture(wrapTouch(event), 'end') 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/echarts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "declaration": true, 6 | "declarationDir": "dist", 7 | "outDir": "dist", 8 | "removeComments": false, 9 | "preserveConstEnums": true, 10 | "moduleResolution": "node", 11 | "experimentalDecorators": true, 12 | "noImplicitAny": false, 13 | "allowSyntheticDefaultImports": true, 14 | "noUnusedLocals": true, 15 | "skipLibCheck": true, 16 | "noUnusedParameters": false, 17 | "strictNullChecks": true, 18 | "sourceMap": true, 19 | "resolveJsonModule": true, 20 | "baseUrl": "src", 21 | "rootDir": "src", 22 | "jsx": "react-jsx", 23 | "typeRoots": [ 24 | "node_modules/@types", 25 | "global.d.ts" 26 | ], 27 | 28 | }, 29 | "compileOnSave": false, 30 | "exclude": [ 31 | "node_modules/**/*", 32 | "dist/*", 33 | ], 34 | "include": [ 35 | "src/**/*" 36 | ], 37 | } 38 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | // generate version/tags 2 | 3 | const { execSync } = require('child_process') 4 | const pkg = require('../packages/echarts/package.json') 5 | const { program } = require('commander') 6 | 7 | program.option('-r, --release ', 'package version') 8 | program.parse() 9 | 10 | const { release } = program.opts() 11 | const tag = `${pkg.name}-v${release}` 12 | try { 13 | if (release) { 14 | const value = execSync(`git tag -l ${tag}`).toString('utf8') 15 | if (!value) { 16 | execSync( 17 | `cd packages/echarts && standard-version -r ${release} -t ${pkg.name}-v --infile ../../CHANGELOG.md`, 18 | ) 19 | execSync(`git push origin ${tag}`) 20 | execSync(`git push origin HEAD`) 21 | } else { 22 | throw `${tag} already exists` 23 | } 24 | } else { 25 | throw 'release does not exist' 26 | } 27 | } catch (e) { 28 | console.log(e) 29 | } 30 | -------------------------------------------------------------------------------- /scripts/update-pkg.js: -------------------------------------------------------------------------------- 1 | // update echarts package.json 2 | 3 | const pkg = require('../packages/echarts/package.json') 4 | const { writeFileSync } = require('fs') 5 | const path = require('path') 6 | if (process.env.NODE_ENV === 'prod') { 7 | Object.assign(pkg, { 8 | main: 'dist/index.js', 9 | module: 'dist/index.esm.js', 10 | }) 11 | } else { 12 | Object.assign(pkg, { 13 | main: 'src/index.ts', 14 | module: 'src/index.ts', 15 | }) 16 | } 17 | 18 | writeFileSync( 19 | path.resolve(process.cwd(), 'packages/echarts/package.json'), 20 | JSON.stringify(pkg, null, 2), 21 | ) 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "declaration": true, 6 | "removeComments": false, 7 | "preserveConstEnums": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "noImplicitAny": false, 11 | "allowSyntheticDefaultImports": true, 12 | "noUnusedLocals": true, 13 | "skipLibCheck": true, 14 | "noUnusedParameters": false, 15 | "strictNullChecks": true, 16 | "sourceMap": true, 17 | "resolveJsonModule": true, 18 | "jsx": "react-jsx", 19 | "typeRoots": [ 20 | "node_modules/@types", 21 | "global.d.ts" 22 | ], 23 | 24 | }, 25 | "compileOnSave": true, 26 | "exclude": [ 27 | "node_modules/**/*", 28 | "dist/*" 29 | ], 30 | "include": [ 31 | "packages/**/*" 32 | ], 33 | } 34 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "pipeline": { 4 | "dev:h5": { 5 | "outputs": ["dist/**"] 6 | }, 7 | "dev:weapp": { 8 | "outputs": ["dist/**"] 9 | }, 10 | "build:echarts": { 11 | "outputs": ["dist/**"] 12 | }, 13 | "publish:echarts": { 14 | "dependsOn": ["build:echarts"] 15 | }, 16 | "publish": { 17 | "dependsOn": ["publish:echarts"] 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------