Compare commits

..

82 Commits
v6.6.2 ... 8.0

Author SHA1 Message Date
36c3cb97a9 chore: release v8.0.0-beta.1 2025-08-10 23:37:53 +08:00
55c68b48b7 chore: release and publish from github actions (#850) 2025-08-10 23:31:34 +08:00
c232e71c47 chore: add echarts 6 features in codegen
* chore: add echarts 6 features in codegen

* update according to 772cf01859
2025-08-10 23:31:34 +08:00
e568005bb2 docs: update version, deps and docs for 8.0 beta (#849)
* chore: up version and deps

* chore: use pnpm CLI to get versions

* add note for echarts 6 upgrade guide

* remove docs script

let cdn to redirect for us

* Revert "remove docs script"

This reverts commit 3bc237db9100864f2813249ac1693735a658e646.

* update demo links
2025-08-10 23:31:23 +08:00
8ed975e09b feat!: inject style via constructable CSSStyleSheet and remove CSP entry (#847)
* chore: not inject inline css on server

* feat!: remove csp entry

* keep csp title in readme

* chore: switch to rolldown and tsdown

* update

* dedupe

* update according to review

* emphasize "both" in csp section

* load css with unplugin-raw

* change tsdown entry
2025-08-10 23:26:17 +08:00
570a26c262 feat: rendering tooltips and dataView with slots (#838)
* feat: experimental component rendered tooltip

* revert slot in VChart

* feat: use tooltip composable

* feat: try createApp

* feat: use pie chart as tooltip

* feat: switch to createVNode

The limitation is that the tooltip detached from the current component tree, not provide/inject

will try teleport next

* feat: try component with teleport

* wip

* add xAxis example

* refactor with shallowReactive

* Support dynamic slot

* fix: fill empty elements with object in array

* shallow copy option along the path

* ssr friendly

* vibe docs

* typo

* update according to the review

* add dataView slot

* chore: fix warnings and errors in demo (#839)

* chore: suppress warning in demo

* chore: prevent multiple intializations of esbuild-wasm in demo HMR

* feat: dynamically update the theme (#841)

Co-authored-by: GU Yiling <justice360@gmail.com>

* feat: add dataView slot

* vibe docs

---------

Co-authored-by: GU Yiling <justice360@gmail.com>

* fix docs typo

* update according to the review

* small fix

* remove wrapper around slotProp

* update comments

* remove anys

* add tooltip slot prop type

* target to vue 3.3

* move slot related codes to slot.ts

---------

Co-authored-by: GU Yiling <justice360@gmail.com>
2025-08-10 23:26:17 +08:00
df640ebce6 chore: remove large mode for flight example (#845) 2025-08-10 23:26:17 +08:00
30e7934aab feat: dynamically update the theme (#841)
Co-authored-by: GU Yiling <justice360@gmail.com>
2025-08-10 23:26:12 +08:00
6155bbb409 chore: fix warnings and errors in demo (#839)
* chore: suppress warning in demo

* chore: prevent multiple intializations of esbuild-wasm in demo HMR
2025-08-10 23:22:04 +08:00
fa42af0723 refactor: use Web Components without native class support detection (#836) 2025-08-10 23:22:04 +08:00
8b7ef5e6e1 refactor: switch to generated .d.ts (#835)
* build: generate d.ts

* fix: preserve PublicMethods

* fix: avoid exposing types of attrs

* refactor: use existing setoption type

* fix: expose root and chart

* feat: use symbol as injection key

* chore: add comment for the type casting of the exposed
2025-08-10 23:22:04 +08:00
522dd7cc5c chore: ESLint Flat Config (#834)
* chore: eslint flat config

* chore: format

* update according to review

* chore: remove prettier config and format

* fix: move handler to script to bypass eslint

* chore: config eslint for lang=js block

* docs: add surrounding empty lines for code block

* chore: also minify css in csp build

* chore: publint
2025-08-10 23:22:04 +08:00
077bd3ec40 build: migrate demo from webpack to Vite (#832) 2025-08-10 23:22:04 +08:00
473fed37a2 chore: remove @vue/runtime-core from peerDependencies
@vue/runtime-core was added here for supporting typescript in vue < 2.7
2025-08-10 23:22:04 +08:00
7ae6892fe6 refactor: change listeners from object to Map 2025-08-10 23:22:03 +08:00
71c106ae29 refactor: rename realListeners to listeners 2025-08-10 23:22:03 +08:00
381489da2f docs: update provide/inject section 2025-08-10 23:22:03 +08:00
2fb0dc2233 refactor: simplify render function 2025-08-10 23:22:03 +08:00
b6c84aab7e feat: support getter in provide/inject 2025-08-10 23:22:03 +08:00
c6a1228c9d docs: remove vue 2 related content 2025-08-10 23:21:47 +08:00
9067505a3a feat!: remove vue 2 2025-08-10 23:19:04 +08:00
d0357c8f31 docs: remove english in Chinese README 2025-08-05 18:03:16 +08:00
cde7c33a32 docs: fix v6 zh docs link 2025-07-08 10:50:35 +08:00
963c204da4 docs: remove showLoading and hideLoading (#842)
* docs: remove showLoading and hideLoading

* docs: add note for `showLoading` and `hideLoading` method
2025-07-02 18:31:57 +08:00
bee810ead3 docs: fix typo in readme cn 2025-06-07 18:00:02 +08:00
1301014626 fix: not auto-resize when the container size is 0, closes #831 2025-05-23 15:10:03 +08:00
b7852ab643 fix: fix type for autoresize (again), closes #810 2024-08-19 23:41:18 +08:00
1378f6e397 build: use upstream version of rollup-plugin-import-css 2024-08-19 11:47:41 +08:00
ec2c15c153 fix(#805): fix style injection 2024-08-13 15:33:36 +08:00
beec26367f fix: fix type for autoresize, closes #800 2024-08-04 07:38:57 +08:00
b366b31afb docs: add peer deps update for echarts in changelog 2024-08-03 16:31:17 +08:00
4f3a6ac0a3 fix: fix event types and bump to 7.0.0 2024-08-03 16:17:46 +08:00
e967bfc1fe docs: update migration part in readme 2024-07-25 23:54:58 +08:00
a9c54e1dc5 docs: update readme 2024-07-25 23:21:49 +08:00
d941e35a61 docs: update csp usage docs 2024-07-24 18:21:56 +08:00
3c063ed21e chore: update changelog and lint config 2024-07-24 18:21:56 +08:00
c0b0d2d0be chore: update changelog 2024-07-24 18:21:56 +08:00
2d310a3891 chore: update linter config 2024-07-24 18:21:56 +08:00
9fe76efa93 chore: update lock file 2024-07-24 18:21:56 +08:00
24ed18e439 docs: use local wasm file 2024-07-24 18:21:56 +08:00
6b98318504 build: add back umd, drop csp, share type between vue2/3 2024-07-24 18:21:56 +08:00
27c79b9012 feat!: move to esm and drop support for vue 2.6 2024-07-24 18:21:56 +08:00
861674352b docs: improve demo 2024-07-24 18:21:56 +08:00
fc53aee8a7 feat!: drop extra wrapper 2024-07-24 18:21:56 +08:00
da0109b07a refactor: improve import statements 2024-07-24 18:21:56 +08:00
7e5c06225c chore: update echarts deps 2024-07-24 18:21:56 +08:00
3c7ff95331 refactor!: remove resize-detector and update vue deps to 2.7+ 2024-07-24 18:21:56 +08:00
d774558f3e chore: fix disappeared logo in demo 2024-06-13 20:25:38 +08:00
5d23006866 chore: update demo 2024-06-11 20:05:05 +08:00
443eaa0367 chore: update readme 2024-06-04 23:14:19 +08:00
16f3f4b419 fix: fix padding style 2024-06-04 23:11:22 +08:00
e651d32334 fix(#783): chart should display after activation in <keep-alive> 2024-05-07 17:43:19 +08:00
a8e30b137a chore: update changelog and version 2024-04-23 19:49:16 +08:00
09808a47d2 fix: move events collecting from init to setup 2024-04-23 18:20:38 +08:00
898195c770 fix: fix esbuild wasm url in demo 2024-04-23 15:59:10 +08:00
07e0d067d5 chore: update deps 2024-04-23 10:55:48 +08:00
185940aecb docs: update changelog and version 2024-04-22 21:15:38 +08:00
def674bc6c refactor: improve readability and add more comments to explain native event handling 2024-04-22 21:08:02 +08:00
feb4f03587 fix: remove native: events from realListeners 2024-04-22 19:22:14 +08:00
5cdff0b4f9 feat: also supports native: in Vue 2 2024-04-22 19:22:14 +08:00
29ff9bc52a feat: support native listener in Vue 3 2024-04-22 19:22:14 +08:00
54d196c9d5 chore: update deps and fix ts error 2024-04-22 12:30:41 +08:00
34defec669 docs: update readme 2024-04-22 11:03:46 +08:00
e189abd9c2 fix(#777): autoresize does not work when reducing height 2024-04-22 11:00:56 +08:00
f07855df08 docs: add sample rate to reduce analytics data points 2024-04-16 13:18:23 +08:00
ae3102a86c chore: add asf notice to readme (#769) 2024-03-12 10:56:23 +08:00
48a2507ef8 chore: add asf notice to readme cn (#770) 2024-03-12 10:55:44 +08:00
311d588850 chore: update version and readme 2024-02-20 10:58:50 +08:00
2f1910ec87 fix: make inner wrapper fit to the root size (#761) 2024-02-20 10:55:38 +08:00
48a0664c2b chore(deps-dev): bump follow-redirects from 1.15.3 to 1.15.4
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-11 17:26:54 +08:00
05ec40a956 chore: improve title 2024-01-02 12:31:56 +08:00
dc9dccbf05 fix: fix postinstall script 2023-12-26 15:21:06 +08:00
476efa4209 fix: add missing type file for 2.7 2023-12-26 15:00:23 +08:00
7bcf57e77b fix(types): fix types for vue < 2.7 2023-12-26 14:54:37 +08:00
ce3ba29209 chore: improve demo 2023-12-26 14:54:04 +08:00
cb95ece9c6 chore: update changelog and version 2023-12-18 10:28:28 +08:00
2a0db561b4 types: fix option type (#751) 2023-12-18 10:22:08 +08:00
2b30334b82 docs: update readme 2023-12-13 11:01:54 +08:00
dbd6699a4f chore: update changelog and version 2023-12-13 11:00:52 +08:00
2130bd6444 fix: fix selector class 2023-12-13 11:00:04 +08:00
e7eb680d46 chore: update changelog and version 2023-12-12 09:59:33 +08:00
b3a10f3654 fix: fix inner wrapper styles 2023-12-12 09:51:27 +08:00
89 changed files with 4690 additions and 9561 deletions

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

View File

@ -1 +0,0 @@
dist

View File

@ -1,25 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
ecmaVersion: 2020,
parser: "@typescript-eslint/parser"
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"vue/multi-word-component-names": "off"
},
overrides: [
{
files: ["*.ts"],
extends: [
"@vue/typescript/recommended",
"@vue/prettier/@typescript-eslint"
]
}
]
};

View File

@ -1,37 +0,0 @@
> ## Help / 帮助
>
> Replace the `[ ]` with `[x]` to check an option. / 将 `[ ]` 替换为 `[x]` 以选择对应选项。
## The type of this issue / Issue 类型
- [ ] Feature request / 新特性需求
- [ ] Bug report / Bug 报告
## Not introduced by ECharts / 非 ECharts 本身问题
Problems about ECharts itself are not handled in this repo. / 本 repo 不负责处理 ECharts 本身的问题。
- [ ] I've checked it's not a problem of ECharts itself. / 我已检查过,这个问题非 ECharts 本身的问题。
## Details / 详情
### Vue version / Vue 版本
- [ ] Vue 3
- [ ] Vue 2
### How are you importing Vue-ECharts? / 你是如何引入 Vue-ECharts 的?
- [ ] Importing `vue-echarts` with a bundler environment / 在 webpack 等打包工具环境下引入 `vue-echarts`
- [ ] Using the global variable by including `<script>` tags / 通过 `<script>` 标签引入全局变量
### The version of Vue-ECharts you are using / Vue-ECharts 的版本
> eg. 6.0.0-beta.5
## Reproduction link / 复现链接
**For bug reports, please provide the steps to reproduce and if possible a minimal demo of the problem. Please paste the link to your CodeSandbox demo below: ([Vue 3 template](https://codesandbox.io/s/charming-night-2y6m6?file=/src/App.vue) / [Vue 2 template](https://codesandbox.io/s/suspicious-glitter-mk66j?file=/src/App.vue))**
**对于 Bug 报告,请在下面提供复现的步骤,最好是最小化的能够重现问题的 demo。请在下方贴入在 CodeSandbox 上 demo 的链接:([Vue 3 模板](https://codesandbox.io/s/charming-night-2y6m6?file=/src/App.vue) / [Vue 2 模板](https://codesandbox.io/s/suspicious-glitter-mk66j?file=/src/App.vue)**

View File

@ -41,6 +41,6 @@ body:
id: repro
attributes:
label: Reproduction
description: "A link to a boiled-down reproduction (a minimal but runnable demo with unnecessary dependencies pruned). If the issue isn't reproducible within an online playground, please create a GitHub repo to reflect the problem. Please paste the link to your CodeSandbox demo or GitHub repo below: ([Vue 3 template](https://codesandbox.io/s/charming-night-2y6m6?file=/src/App.vue) / [Vue 2 template](https://codesandbox.io/s/suspicious-glitter-mk66j?file=/src/App.vue))"
description: "A link to a boiled-down reproduction (a minimal but runnable demo with unnecessary dependencies pruned). If the issue isn't reproducible within an online playground, please create a GitHub repo to reflect the problem. Please paste the link to your [StackBlitz demo](https://stackblitz.com/edit/vue-echarts-8?file=src%2FApp.vue) or GitHub repo below:"
validations:
required: true

View File

@ -41,6 +41,6 @@ body:
id: repro
attributes:
label: 问题复现
description: "请提供一个精炼的问题复现(去除无关依赖的最小化可运行 demo。如果在线环境无法复现可以创建对应的 GitHub repo 来提供复现环境。请在下方贴入在 CodeSandbox 上 demo 的链接或 GitHub repo 链接:([Vue 3 模板](https://codesandbox.io/s/charming-night-2y6m6?file=/src/App.vue) / [Vue 2 模板](https://codesandbox.io/s/suspicious-glitter-mk66j?file=/src/App.vue)"
description: "请提供一个精炼的问题复现(去除无关依赖的最小化可运行 demo。如果在线环境无法复现可以创建对应的 GitHub repo 来提供复现环境。请在下方贴入 [StackBlitz demo](https://stackblitz.com/edit/vue-echarts-8?file=src%2FApp.vue) 的链接或 GitHub repo 链接:"
validations:
required: true

38
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: CI
on:
pull_request:
push:
branches:
- main
- "8.0" # remove this after 8.0 is merged into main
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Lint
run: pnpm run lint
- name: Typecheck
run: pnpm run typecheck && pnpm run dev:typecheck
- name: Build
run: pnpm run build
- name: Publint
run: pnpm run publint

52
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Release
on:
push:
tags:
- "v**"
permissions:
id-token: write
contents: write
jobs:
release:
runs-on: ubuntu-latest
environment: npm-publish
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Extract release notes
run: pnpm releaselog --format=notes ${{ github.ref_name }} > RELEASE_NOTES.md
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
body_path: RELEASE_NOTES.md
prerelease: ${{ contains(github.ref_name, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build
run: pnpm run build
- name: Get dist tag
id: tag
run: echo "tag=$(pnpm exec jiti scripts/dist-tag.ts '${{ github.ref_name }}')" >> $GITHUB_OUTPUT
- name: Publish to npm
run: npm i -g npm && pnpm publish --access public --tag ${{ steps.tag.outputs.tag }} --no-git-checks

2
.gitignore vendored
View File

@ -22,4 +22,4 @@ pnpm-debug.log*
*.sln
*.sw?
/demo
/demo/dist

1
.npmrc
View File

@ -1 +0,0 @@
strict-peer-dependencies = false

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
pnpm-lock.yaml
demo/data/*.json
src/style.css

View File

@ -1,4 +0,0 @@
{
"trailingComma": "none",
"arrowParens": "avoid"
}

View File

@ -1,4 +0,0 @@
{
"installDependencies": true,
"startCommand": "pnpm serve"
}

View File

@ -1,399 +1,504 @@
## 8.0.0-beta.1
### Breaking changes
- Updated peer dependency for `echarts` to `^6.0.0`.
- Updated peer dependency for `vue` to `^3.3.0`.
- Dropped support for browsers without native `class` support.
- Removed `vue-echarts/csp` entry. Use `vue-echarts` instead. Only manually include `vue-echarts/style.css` if you are **both** enforcing a strict CSP that prevents inline `<style>` injection and targeting browsers that don't support the [`CSSStyleSheet()` constructor](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility).
### New features
- ECharts 6 support.
- Added slots for tooltip and data view.
- Added support for getter in provide/inject.
### Chore
- Built with tsdown.
- Switched Demo from Webpack to Rolldown-Vite.
- Use ESLint flat config.
## 7.0.3
- Fixed type for `autoresize` (again).
## 7.0.2
- Fixed style injection regression (#805).
## 7.0.1
- Fixed type for `autoresize`.
## 7.0.0
> Other prerelease changes:
>
> - [7.0.0-beta.0](#700-beta0)
- Fixed types for events.
## 7.0.0-beta.0
- Upgraded to ESM.
- Removed the `postinstall` script.
- API remains the same.
### Breaking changes
- Peer deps for `echarts` is upgraded to `^5.5.1`.
- Dropped support for browsers without `ResizeObserver`. Can work with [resize-observer-polyfill](https://www.npmjs.com/package/resize-observer-polyfill).
- Dropped support for Vue < 2.7.
- Dropped CJS outputs.
- Dropped extra wrapper element so that you should no longer set `padding` on the component root.
- For strict CSP usage, `vue-echarts/csp` should now be used as the entry point and `vue-echarts/csp/style.css` should be included manually.
## 6.7.3
- Fixed that `padding` on the component root doesn't work.
## 6.7.2
- Fixed that charts inside `<keep-alive>` failed to display after activation.
## 6.7.1
- Fixed that native events won't actually trigger.
## 6.7.0
- Added supports for native DOM events binding with the `native:` prefix.
## 6.6.10
- Fixed that `autoresize` doesn't work when reducing the height or the root element.
## 6.6.9
- Fixed that the chart may not be the same size as the component root element ([#761](https://github.com/ecomfe/vue-echarts/issues/761)).
## 6.6.8
- Fixed the postinstall script to patch the correct `types` entry for Vue 2.7.
## 6.6.7
- Added missing type file for Vue 2.7.
## 6.6.6
- Fixed types for Vue < 2.7.
## 6.6.5
- Fixed type for `option` regressed in v6.6.2.
## 6.6.4
- Fixed style regression introduced by v6.6.3.
## 6.6.3
- Fixed inner wrapper styles.
## 6.6.2
* Fixed that tooltips may affected by internal styling by VueECharts.
- Fixed that tooltips may affected by internal styling by VueECharts.
## 6.6.1
* Make `padding` work out-of-the-box.
- Make `padding` work out-of-the-box.
## 6.6.0
* Added support for `autoresize` accepting an options object to specify custom throttle delay or resize callback.
- Added support for `autoresize` accepting an options object to specify custom throttle delay or resize callback.
## 6.5.5
* Removed the custom element registration enhancement for strict CSP builds so that they won't contain `new Function`.
- Removed the custom element registration enhancement for strict CSP builds so that they won't contain `new Function`.
## 6.5.4
* Cleaned up the `console.log` call sneaked in by mistake.
- Cleaned up the `console.log` call sneaked in by mistake.
## 6.5.3
* Fixed default behavior for `notMerge` option (#691).
- Fixed default behavior for `notMerge` option (#691).
## 6.5.2
* Added `dist/csp/*` to support strict CSP with extracted CSS file.
- Added `dist/csp/*` to support strict CSP with extracted CSS file.
## 6.5.1
* Fixed types for mouse events.
- Fixed types for mouse events.
## 6.5.0
* Use more precise typings for all event params.
* Updated peer deps for `echarts` to `^5.4.1`.
- Use more precise typings for all event params.
- Updated peer deps for `echarts` to `^5.4.1`.
## 6.4.1
* Improve typings for mouse event params.
- Improve typings for mouse event params.
## 6.4.0
* Delay the disposal of the ECharts instance to the moment the element is disconnected from the DOM if possible (#433).
- Delay the disposal of the ECharts instance to the moment the element is disconnected from the DOM if possible (#433).
## 6.3.3
* Make autoresize work for grid layout by default (#675).
- Make autoresize work for grid layout by default (#675).
## 6.3.2
* Added basic types for events (only event names).
- Added basic types for events (only event names).
## 6.3.1
* Revert the style change to prevent tooltips from being clipped.
- Revert the style change to prevent tooltips from being clipped.
## 6.3.0
* Injected values can now be wrapped in an object so that they can be reactive in Vue 2.
- Injected values can now be wrapped in an object so that they can be reactive in Vue 2.
## 6.2.4
* Fixed that attributes were not outputted onto the chart root element for Vue 2 (#670).
- Fixed that attributes were not outputted onto the chart root element for Vue 2 (#670).
## 6.2.3
* Fixed the problem that `v-on` stops working after upgrading to `vue@2.7.x`.
- Fixed the problem that `v-on` stops working after upgrading to `vue@2.7.x`.
## 6.2.2
* Improve types for `update-options`.
- Improve types for `update-options`.
## 6.2.1
* Improved types for provide/inject API.
- Improved types for provide/inject API.
## 6.2.0
* Added support for Vue 2.7+.
- Added support for Vue 2.7+.
## 6.1.0
* Added support for `.once` event modifier.
- Added support for `.once` event modifier.
## 6.0.3
* Improved typings for Vue 2 version.
- Improved typings for Vue 2 version.
## 6.0.2
* Make `notMerge` option still respect `update-options`.
* The default behavior of `notMerge` now revert to checking if there is a reference change for the `option` prop.
- Make `notMerge` option still respect `update-options`.
- The default behavior of `notMerge` now revert to checking if there is a reference change for the `option` prop.
## 6.0.1
* Update should always be `notMerge: true`.
* Update dependency version for vue-demi.
- Update should always be `notMerge: true`.
- Update dependency version for vue-demi.
## 6.0.0
* Update dependency versions.
- Update dependency versions.
## 6.0.0-rc.6
* Revert the change of `updateOptions.lazyUpdate`. It defaults to `false` again.
* Fixed the occasional error caused by the internal implementation of ECharts.
* Removed unexpected `console.log` call.
- Revert the change of `updateOptions.lazyUpdate`. It defaults to `false` again.
- Fixed the occasional error caused by the internal implementation of ECharts.
- Removed unexpected `console.log` call.
## 6.0.0-rc.5
* Changed `updateOptions.lazyUpdate` to `true` by default. ([#533](https://github.com/ecomfe/vue-echarts/issues/533#issuecomment-809883909))
* Only perform an additional `resize` call after init within a task. ([#533](https://github.com/ecomfe/vue-echarts/issues/533#issuecomment-809883909))
* The `.chart` getter API now works for Vue 2. (#542)
- Changed `updateOptions.lazyUpdate` to `true` by default. ([#533](https://github.com/ecomfe/vue-echarts/issues/533#issuecomment-809883909))
- Only perform an additional `resize` call after init within a task. ([#533](https://github.com/ecomfe/vue-echarts/issues/533#issuecomment-809883909))
- The `.chart` getter API now works for Vue 2. (#542)
## 6.0.0-rc.4
* Fix type error for `Vue2` reference.
- Fix type error for `Vue2` reference.
## 6.0.0-rc.3
* Add missing types file for Vue 2.
- Add missing types file for Vue 2.
## 6.0.0-rc.2
* Fix postinstall script.
- Fix postinstall script.
## 6.0.0-rc.1
* Move inital resize timing earlier into microtasks so that minimize visual layout shift.
* Add a postinstall script to bail out type check for Vue 2 environment.
- Move inital resize timing earlier into microtasks so that minimize visual layout shift.
- Add a postinstall script to bail out type check for Vue 2 environment.
## 6.0.0-beta.7
* Ensure charts fit to container after the next UI render. (#518)
- Ensure charts fit to container after the next UI render. (#518)
## 6.0.0-beta.6
* Ensure VCA is always installed.
- Ensure VCA is always installed.
## 6.0.0-beta.5
* Remove deps for `mergeProps` as it's not yet implemented in `@vue/composition-api`. (#519)
- Remove deps for `mergeProps` as it's not yet implemented in `@vue/composition-api`. (#519)
## 6.0.0-beta.4
* Suppress native events and only handles chart events. (#516)
- Suppress native events and only handles chart events. (#516)
## 6.0.0-beta.3
* Update `vue-demi` version to fix type error.
- Update `vue-demi` version to fix type error.
## 6.0.0-beta.2
* Fix injection keys for UMD bundle.
* Add `vue-demi` to UMD bundle.
- Fix injection keys for UMD bundle.
- Add `vue-demi` to UMD bundle.
## 6.0.0-beta.1
* Use a custom element for the root element to make default style less specific.
- Use a custom element for the root element to make default style less specific.
## 6.0.0-alpha.5
* Fix event support for Vue 2.
- Fix event support for Vue 2.
## 6.0.0-alpha.4
* Add missing injection key exports.
- Add missing injection key exports.
## 6.0.0-alpha.3
* Add missing dependencies for `vue-demi` and `resize-detector`.
- Add missing dependencies for `vue-demi` and `resize-detector`.
## 6.0.0-alpha.2
* Fix bundling for UMD build.
- Fix bundling for UMD build.
## 6.0.0-alpha.1
### Breaking changes
* Update peer dependency for `echarts` to `^5.0.2`.
* Update peer dependency for `vue` to `^2.6.11 || ^3.0.0`.
* Now `@vue/composition-api` is required to be installed to use Vue-ECharts with Vue 2.
* `options` is renamed to **`option`** to align with ECharts itself.
* Updating `option` will respect **`update-options`** configs instead of checking reference change.
* `watch-shallow` is removed. Use **`manual-update`** for performance critical scenarios.
* `mergeOptions` is renamed to **`setOption`** to align with ECharts itself.
* `showLoading` and `hideLoading` is removed. Use the **`loading` and `loading-options`** props instead.
* `appendData` is removed. (Due to ECharts 5's breaking change.)
* All static methods are removed from `vue-echarts`. Use those methods from `echarts` directly.
* Computed getters (`width`, `height`, `isDisposed` and `computedOptions`) are removed. Use the **`getWidth`, `getHeight`, `isDisposed` and `getOption`** methods instead.
* Now the root element of the component have **`100%×100%`** size by default, instead of `600×400`.
- Update peer dependency for `echarts` to `^5.0.2`.
- Update peer dependency for `vue` to `^2.6.11 || ^3.0.0`.
- Now `@vue/composition-api` is required to be installed to use Vue-ECharts with Vue 2.
- `options` is renamed to **`option`** to align with ECharts itself.
- Updating `option` will respect **`update-options`** configs instead of checking reference change.
- `watch-shallow` is removed. Use **`manual-update`** for performance critical scenarios.
- `mergeOptions` is renamed to **`setOption`** to align with ECharts itself.
- `showLoading` and `hideLoading` is removed. Use the **`loading` and `loading-options`** props instead.
- `appendData` is removed. (Due to ECharts 5's breaking change.)
- All static methods are removed from `vue-echarts`. Use those methods from `echarts` directly.
- Computed getters (`width`, `height`, `isDisposed` and `computedOptions`) are removed. Use the **`getWidth`, `getHeight`, `isDisposed` and `getOption`** methods instead.
- Now the root element of the component have **`100%×100%`** size by default, instead of `600×400`.
### New features
* ECharts 5 support.
* Vue 3 support.
* TypeScript support.
* Add new `update-options` prop and support providing default from context.
* Add new `loading` prop and support providing default from context.
* Add new `loading-options` prop and support providing default from context.
* Support providing default from context for the `theme` prop.
- ECharts 5 support.
- Vue 3 support.
- TypeScript support.
- Add new `update-options` prop and support providing default from context.
- Add new `loading` prop and support providing default from context.
- Add new `loading-options` prop and support providing default from context.
- Support providing default from context for the `theme` prop.
## 5.0.0-beta.0
* Update peer dependency for `vue` to `^2.4.0`. **BREAKING**
- Update peer dependency for `vue` to `^2.4.0`. **BREAKING**
## 4.1.0
* Fix the problem that `mergeOptions` didn't use the correct options if the instance is inited on-the-fly.
* Expose ZRender events via `zr:` prefixed events.
* Update to `echarts@4.5.0` (only affects the bundled version).
- Fix the problem that `mergeOptions` didn't use the correct options if the instance is inited on-the-fly.
- Expose ZRender events via `zr:` prefixed events.
- Update to `echarts@4.5.0` (only affects the bundled version).
## 4.0.4
* Update to `echarts@4.3.0` (only affects the bundled version).
- Update to `echarts@4.3.0` (only affects the bundled version).
## 4.0.3
* Update to `resize-detector@0.1.10`.
- Update to `resize-detector@0.1.10`.
## 4.0.2
* Make `manual-update` truely responsive.
- Make `manual-update` truely responsive.
## 4.0.1
* Fix `legendscroll` event.
- Fix `legendscroll` event.
## 4.0.0
* Release 4.0.0.
- Release 4.0.0.
## 4.0.0-beta.1
* Fix autoresize.
- Fix autoresize.
## 4.0.0-beta.0
* Move `echarts` into `peerDependencies`. **BREAKING**
* Rename `auto-resize` to `autoresize`. **BREAKING**
* Point `module` entry to the source version. **BREAKING**
* Switch to Vue CLI 3 for demo.
- Move `echarts` into `peerDependencies`. **BREAKING**
- Rename `auto-resize` to `autoresize`. **BREAKING**
- Point `module` entry to the source version. **BREAKING**
- Switch to Vue CLI 3 for demo.
## 3.1.2
* Fix the problem that `setOption` is always called with `notMerge: true`.
- Fix the problem that `setOption` is always called with `notMerge: true`.
## 3.1.1
* Fix the problem that `options` are not watched as expected.
- Fix the problem that `options` are not watched as expected.
## 3.1.0
* Add `manual-update` prop to handle performance critical scenarios.
* Deprecate `watch-shallow` prop as it was actually not working as expected.
* Fix the computed getters by using `Object.defineProperties` directly instead of Vue's `computed` as it no longer works as expected after Vue 2.0.
* Remove `chart` from `data` to gain a performance boost.
- Add `manual-update` prop to handle performance critical scenarios.
- Deprecate `watch-shallow` prop as it was actually not working as expected.
- Fix the computed getters by using `Object.defineProperties` directly instead of Vue's `computed` as it no longer works as expected after Vue 2.0.
- Remove `chart` from `data` to gain a performance boost.
## 3.0.9
* Update to `resize-detector@0.1.7` to better handle initial resize callback.
- Update to `resize-detector@0.1.7` to better handle initial resize callback.
## 3.0.8
* Add new events and API to adapt the latest version of ECharts.
- Add new events and API to adapt the latest version of ECharts.
## 3.0.7
* Only apply optimization introduce in last version for charts resize from `0` area.
- Only apply optimization introduce in last version for charts resize from `0` area.
## 3.0.6
* Optimize `auto-resize` for initially hidden (`display: none`) charts.
- Optimize `auto-resize` for initially hidden (`display: none`) charts.
## 3.0.5
* Update to `resize-detector@0.1.5`.
- Update to `resize-detector@0.1.5`.
## 3.0.4
* Fix misused `MutationObserver` (#200).
- Fix misused `MutationObserver` (#200).
## 3.0.3
* Update to `resize-detector@0.1.2`.
- Update to `resize-detector@0.1.2`.
## 3.0.2
* Update ECharts to `4.0.2`.
- Update ECharts to `4.0.2`.
## 3.0.1
* Fix npm distribution.
- Fix npm distribution.
## 3.0.0
* Added support for ECharts 4.
* `auto-resize` now listens to element size change instead of window.
* Remove deprecated `chart` prefixed events.
- Added support for ECharts 4.
- `auto-resize` now listens to element size change instead of window.
- Remove deprecated `chart` prefixed events.
## 2.6.0
* Added `watchShallow` prop to manually disable deep watch on `options` to optimize performance for charts with large amout of data.
* Made all props reactive.
* Updated ECharts dependency to `^3.8.5`.
- Added `watchShallow` prop to manually disable deep watch on `options` to optimize performance for charts with large amout of data.
- Made all props reactive.
- Updated ECharts dependency to `^3.8.5`.
## 2.5.1
* Updated ECharts dependency to `3.8.2`+ to fix module breaking change introduced in `3.8.0`.
- Updated ECharts dependency to `3.8.2`+ to fix module breaking change introduced in `3.8.0`.
## 2.5.0
* Fixed collision with Vue's internal methods by removing `_` prefix.
* `mergeOptions` now accept same arguments as ECharts' `setOption` method.
* Updated ECharts dependency to 3.7.2+.
- Fixed collision with Vue's internal methods by removing `_` prefix.
- `mergeOptions` now accept same arguments as ECharts' `setOption` method.
- Updated ECharts dependency to 3.7.2+.
## 2.4.1
* Made `theme` reactive.
* Added `focusnodeadjacency` & `unfocusnodeadjacency` events.
* Fixed the problem that charts won't refresh after `keep-alive` components are activated.
- Made `theme` reactive.
- Added `focusnodeadjacency` & `unfocusnodeadjacency` events.
- Fixed the problem that charts won't refresh after `keep-alive` components are activated.
## 2.4.0
* Add `computedOptions`.
- Add `computedOptions`.
## 2.3.9
* Replace publish npm scripts with shell commands to prevent failure upon npm install.
- Replace publish npm scripts with shell commands to prevent failure upon npm install.
## 2.3.8
* Fixed the problem that styles are missing for precompiled version.
- Fixed the problem that styles are missing for precompiled version.
## 2.3.7
* Switch back to `Vue.util.warn`.
* Switch build tool to rollup.
- Switch back to `Vue.util.warn`.
- Switch build tool to rollup.
## 2.3.6
* Hot fix for last version. Use `console.warn` temporarily.
- Hot fix for last version. Use `console.warn` temporarily.
## 2.3.5
* Mark Vue as an external dependency in webpack config.
- Mark Vue as an external dependency in webpack config.
## 2.3.4
* Use `Vue.util.warn` directly.
- Use `Vue.util.warn` directly.
## 2.3.3
* Fix NPM package.
- Fix NPM package.
## 2.3.2
* Fix the implementation of `disconnect`.
- Fix the implementation of `disconnect`.
## 2.3.1
* Correctly dispose ECharts instance before component is destroyed.
* Fix the problem that `group` is not properly initialized.
- Correctly dispose ECharts instance before component is destroyed.
- Fix the problem that `group` is not properly initialized.
## 2.3.0
* As native events are now not listened by `v-on` in Vue.js 2.0, change mouse events name to original ones (keeping emitting `chart*` events for now).
* Fix getter for `width` / `height` / `isDisposed`.
* `options` is now optional to initialize the component and the chart will be initialized automatically when `options` is set.
- As native events are now not listened by `v-on` in Vue.js 2.0, change mouse events name to original ones (keeping emitting `chart*` events for now).
- Fix getter for `width` / `height` / `isDisposed`.
- `options` is now optional to initialize the component and the chart will be initialized automatically when `options` is set.
## 2.2.0
* Add `auto-resize`.
* Refined demo.
- Add `auto-resize`.
- Refined demo.
## 2.1.0
* Fix `disconnect`.
* When importing `ECharts.vue`, only ECharts core will be imported instead of the whole ECharts bundle.
- Fix `disconnect`.
- When importing `ECharts.vue`, only ECharts core will be imported instead of the whole ECharts bundle.
## 2.0.0
* Update Vue dependency to `2.0.1`.
* Add support for new methods & events for ECharts.
* Fix missing arguments for some APIs.
- Update Vue dependency to `2.0.1`.
- Add support for new methods & events for ECharts.
- Fix missing arguments for some APIs.
## 0.1.2
* Update ECharts version.
* Remove unnecessary files from NPM package.
- Update ECharts version.
- Remove unnecessary files from NPM package.
## 0.1.1
* Fix usage in README.
- Fix usage in README.
## 0.1.0
* First version.
- First version.

470
README.md
View File

@ -1,50 +1,25 @@
<h1 align="center">Vue-ECharts</h1>
<p align="center">Vue.js <sup>(v2/v3)</sup> component for Apache ECharts <sup>(v5)</sup>.</p>
<p align="center"><a href="https://vue-echarts.dev/">View Demo →</a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"/></a> <a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"><img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"/></a></p>
> [!IMPORTANT]
> We have released an [import code generator](https://vue-echarts.dev/#codegen) that can generate precise import code by pasting the `option` code.
>
> ![](https://github.com/ecomfe/vue-echarts/assets/1726061/f9c38a06-3422-4f0e-ab8c-f242d9aea9aa)
>
> [Try it →](https://vue-echarts.dev/#codegen)
<p align="center">Vue.js component for Apache ECharts™.</p>
<p align="center"><a href="https://npmjs.com/package/vue-echarts"><img alt="npm version" src="https://img.shields.io/npm/v/vue-echarts"></a> <a href="https://vue-echarts.dev/"><img src="https://img.shields.io/badge/Demo%20%C2%BB-20c3aa" alt="View demo"></a> <a href="./README.zh-Hans.md"><img src="https://img.shields.io/badge/%E4%B8%AD%E6%96%87%E7%89%88%20%C2%BB-000" alt="前往中文版"></a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"></a> <a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"><img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"></a></p>
---
<h2>💡 Heads up 💡 <a href="./README.zh-Hans.md"><img src="https://img.shields.io/badge/%F0%9F%87%A8%F0%9F%87%B3-%E4%B8%AD%E6%96%87%E7%89%88-white?labelColor=white" alt="前往中文版" align="right" height="24"/></a></h2>
If you are migrating from `vue-echarts` ≤ 5, you should read the _[Migration to v6](#migration-to-v6)_ section before you update to v6.
Not ready yet? Read documentation for older versions [here →](https://github.com/ecomfe/vue-echarts/tree/5.x)
> Still using Vue 2? Read v7 docs [here →](https://github.com/ecomfe/vue-echarts/tree/7.x)
## Installation & Usage
### npm & ESM
### npm
```bash
```sh
npm install echarts vue-echarts
```
To make `vue-echarts` work for _Vue 2_ (<2.7.0), you need to have `@vue/composition-api` installed:
```sh
npm i @vue/composition-api
```
If you are using _NuxtJS_ on top of _Vue 2_, you'll also need `@nuxtjs/composition-api`:
```sh
npm i @nuxtjs/composition-api
```
And then add `'@nuxtjs/composition-api/module'` in the `buildModules` option in your `nuxt.config.js`.
#### Example
<details>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-vue-3?file=src%2FApp.vue">Demo →</a></summary>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-8?file=src%2FApp.vue">Demo →</a></summary>
```vue
<template>
@ -58,7 +33,7 @@ import { PieChart } from "echarts/charts";
import {
TitleComponent,
TooltipComponent,
LegendComponent
LegendComponent,
} from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
import { ref, provide } from "vue";
@ -68,7 +43,7 @@ use([
PieChart,
TitleComponent,
TooltipComponent,
LegendComponent
LegendComponent,
]);
provide(THEME_KEY, "dark");
@ -76,16 +51,16 @@ provide(THEME_KEY, "dark");
const option = ref({
title: {
text: "Traffic Sources",
left: "center"
left: "center",
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
legend: {
orient: "vertical",
left: "left",
data: ["Direct", "Email", "Ad Networks", "Video Ads", "Search Engines"]
data: ["Direct", "Email", "Ad Networks", "Video Ads", "Search Engines"],
},
series: [
{
@ -98,17 +73,17 @@ const option = ref({
{ value: 310, name: "Email" },
{ value: 234, name: "Ad Networks" },
{ value: 135, name: "Video Ads" },
{ value: 1548, name: "Search Engines" }
{ value: 1548, name: "Search Engines" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)"
}
}
}
]
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
});
</script>
@ -121,103 +96,11 @@ const option = ref({
</details>
<details>
<summary>Vue 2 <a href="https://stackblitz.com/edit/vue-echarts-vue-2?file=src%2FApp.vue">Demo →</a></summary>
```vue
<template>
<v-chart class="chart" :option="option" />
</template>
<script>
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { PieChart } from "echarts/charts";
import {
TitleComponent,
TooltipComponent,
LegendComponent
} from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
use([
CanvasRenderer,
PieChart,
TitleComponent,
TooltipComponent,
LegendComponent
]);
export default {
name: "HelloWorld",
components: {
VChart
},
provide: {
[THEME_KEY]: "dark"
},
data() {
return {
option: {
title: {
text: "Traffic Sources",
left: "center"
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: "vertical",
left: "left",
data: [
"Direct",
"Email",
"Ad Networks",
"Video Ads",
"Search Engines"
]
},
series: [
{
name: "Traffic Sources",
type: "pie",
radius: "55%",
center: ["50%", "60%"],
data: [
{ value: 335, name: "Direct" },
{ value: 310, name: "Email" },
{ value: 234, name: "Ad Networks" },
{ value: 135, name: "Video Ads" },
{ value: 1548, name: "Search Engines" }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)"
}
}
}
]
}
};
}
};
</script>
<style scoped>
.chart {
height: 400px;
}
</style>
```
</details>
> [!IMPORTANT]
> We encourage manually importing components and charts from ECharts for smaller bundle size. We've built an [import code generator](https://vue-echarts.dev/#codegen) to help you with that. You can just paste in your `option` code and we'll generate the precise import code for you.
>
> ![](https://github.com/ecomfe/vue-echarts/assets/1726061/f9c38a06-3422-4f0e-ab8c-f242d9aea9aa)
>
> [Try it →](https://vue-echarts.dev/#codegen)
But if you really want to import the whole ECharts bundle without having to import modules manually, just add this in your code:
@ -226,19 +109,21 @@ But if you really want to import the whole ECharts bundle without having to impo
import "echarts";
```
### CDN & Global variable
### CDN
Drop `<script>` inside your HTML file and access the component via `window.VueECharts`.
<details>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-vue-3-global?file=index.html">Demo →</a></summary>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-8-global?file=index.html">Demo →</a></summary>
<!-- vue3Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.7"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.1"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.18"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@8.0.0-beta.1"></script>
```
<!-- vue3Scripts:end -->
```js
@ -250,25 +135,7 @@ app.component('v-chart', VueECharts)
</details>
<details>
<summary>Vue 2 <a href="https://stackblitz.com/edit/vue-echarts-vue-2-global?file=index.html">Demo →</a></summary>
<!-- vue2Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.15"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.1"></script>
```
<!-- vue2Scripts:end -->
```js
// register globally (or you can do it locally)
Vue.component("v-chart", VueECharts);
```
</details>
See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/src/demo).
See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/demo).
### Props
@ -288,7 +155,8 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/src/dem
ECharts' universal interface. Modifying this prop will trigger ECharts' `setOption` method. Read more [here →](https://echarts.apache.org/en/option.html)
> 💡 When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, ` notMerge: true` will be specified.
> [!TIP]
> When `update-options` is not specified, `notMerge: false` will be specified by default when the `setOption` method is called if the `option` object is modified directly and the reference remains unchanged; otherwise, if a new reference is bound to `option`, `notMerge: true` will be specified.
- `update-options: object`
@ -318,89 +186,6 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/src/dem
For performance critical scenarios (having a large dataset) we'd better bypass Vue's reactivity system for `option` prop. By specifying `manual-update` prop with `true` and not providing `option` prop, the dataset won't be watched any more. After doing so, you need to retrieve the component instance with `ref` and manually call `setOption` method to update the chart.
### Provide / Inject
Vue-ECharts provides provide/inject API for `theme`, `init-options`, `update-options` and `loading-options` to help configuring contextual options. eg. for `init-options` you can use the provide API like this:
<details>
<summary>Vue 3</summary>
```js
import { THEME_KEY } from 'vue-echarts'
import { provide } from 'vue'
// composition API
provide(THEME_KEY, 'dark')
// options API
{
provide: {
[THEME_KEY]: 'dark'
}
}
```
</details>
<details>
<summary>Vue 2</summary>
```js
import { THEME_KEY } from 'vue-echarts'
// in component options
{
provide: {
[THEME_KEY]: 'dark'
}
}
```
> **Note**
>
> You need to provide an object for Vue 2 if you want to change it dynamically.
>
> ```js
> // in component options
> {
> data () {
> return {
> theme: { value: 'dark' }
> }
> },
> provide () {
> return {
> [THEME_KEY]: this.theme
> }
> }
> }
> ```
</details>
### Methods
- `setOption` [](https://echarts.apache.org/en/api.html#echartsInstance.setOption)
- `getWidth` [](https://echarts.apache.org/en/api.html#echartsInstance.getWidth)
- `getHeight` [](https://echarts.apache.org/en/api.html#echartsInstance.getHeight)
- `getDom` [](https://echarts.apache.org/en/api.html#echartsInstance.getDom)
- `getOption` [](https://echarts.apache.org/en/api.html#echartsInstance.getOption)
- `resize` [](https://echarts.apache.org/en/api.html#echartsInstance.resize)
- `dispatchAction` [](https://echarts.apache.org/en/api.html#echartsInstance.dispatchAction)
- `convertToPixel` [](https://echarts.apache.org/en/api.html#echartsInstance.convertToPixel)
- `convertFromPixel` [](https://echarts.apache.org/en/api.html#echartsInstance.convertFromPixel)
- `containPixel` [](https://echarts.apache.org/en/api.html#echartsInstance.containPixel)
- `showLoading` [](https://echarts.apache.org/en/api.html#echartsInstance.showLoading)
- `hideLoading` [](https://echarts.apache.org/en/api.html#echartsInstance.hideLoading)
- `getDataURL` [](https://echarts.apache.org/en/api.html#echartsInstance.getDataURL)
- `getConnectedDataURL` [](https://echarts.apache.org/en/api.html#echartsInstance.getConnectedDataURL)
- `clear` [](https://echarts.apache.org/en/api.html#echartsInstance.clear)
- `dispose` [](https://echarts.apache.org/en/api.html#echartsInstance.dispose)
### Static Methods
Static methods can be accessed from [`echarts` itself](https://echarts.apache.org/en/api.html#echarts).
### Events
You can bind events with Vue's `v-on` directive.
@ -411,8 +196,7 @@ You can bind events with Vue's `v-on` directive.
</template>
```
> **Note**
>
> [!NOTE]
> Only the `.once` event modifier is supported as other modifiers are tightly coupled with the DOM event system.
Vue-ECharts support the following events:
@ -463,46 +247,194 @@ Vue-ECharts support the following events:
See supported events [here →](https://echarts.apache.org/en/api.html#events)
## CSP: `style-src` or `style-src-elem`
#### Native DOM Events
If you are applying a CSP to prevent inline `<style>` injection, you need to use files from `dist/csp` directory and include `dist/csp/style.css` into your app manually.
As Vue-ECharts binds events to the ECharts instance by default, there is some caveat when using native DOM events. You need to prefix the event name with `native:` to bind native DOM events.
## Migration to v6
```vue
<template>
<v-chart @native:click="handleClick" />
</template>
```
> 💡 Please make sure to read the [migration guide](https://echarts.apache.org/en/tutorial.html#ECharts%205%20Upgrade%20Guide) for ECharts 5 as well.
### Provide / Inject
The following breaking changes are introduced in `vue-echarts@6`:
Vue-ECharts provides provide/inject API for `theme`, `init-options`, `update-options` and `loading-options` to help configuring contextual options. eg. for `theme` you can use the provide API like this:
### Vue 2 support
<details>
<summary>Composition API</summary>
- If you are using version prior to `vue@2.7.0`, `@vue/composition-api` is required to be installed to use Vue-ECharts with Vue 2.
```js
import { THEME_KEY } from "vue-echarts";
import { provide } from "vue";
### Props
provide(THEME_KEY, "dark");
- `options` is renamed to **`option`** to align with ECharts itself.
- Updating `option` will respect **`update-options`** configs instead of checking reference change.
- `watch-shallow` is removed. Use **`manual-update`** for performance critical scenarios.
// or provide a ref
const theme = ref("dark");
provide(THEME_KEY, theme);
// getter is also supported
provide(THEME_KEY, () => theme.value);
```
</details>
<details>
<summary>Options API</summary>
```js
import { THEME_KEY } from 'vue-echarts'
import { computed } from 'vue'
export default {
{
provide: {
[THEME_KEY]: 'dark'
}
}
}
// Or make injections reactive
export default {
data() {
return {
theme: 'dark'
}
},
provide() {
return {
[THEME_KEY]: computed(() => this.theme)
}
}
}
```
</details>
### Methods
- `mergeOptions` is renamed to **`setOption`** to align with ECharts itself.
- `showLoading` and `hideLoading` is removed. Use the **`loading` and `loading-options`** props instead.
- `appendData` is removed. (Due to ECharts 5's breaking change.)
- All static methods are removed from `vue-echarts`. Use those methods from `echarts` directly.
- `setOption` [](https://echarts.apache.org/en/api.html#echartsInstance.setOption)
- `getWidth` [](https://echarts.apache.org/en/api.html#echartsInstance.getWidth)
- `getHeight` [](https://echarts.apache.org/en/api.html#echartsInstance.getHeight)
- `getDom` [](https://echarts.apache.org/en/api.html#echartsInstance.getDom)
- `getOption` [](https://echarts.apache.org/en/api.html#echartsInstance.getOption)
- `resize` [](https://echarts.apache.org/en/api.html#echartsInstance.resize)
- `dispatchAction` [](https://echarts.apache.org/en/api.html#echartsInstance.dispatchAction)
- `convertToPixel` [](https://echarts.apache.org/en/api.html#echartsInstance.convertToPixel)
- `convertFromPixel` [](https://echarts.apache.org/en/api.html#echartsInstance.convertFromPixel)
- `containPixel` [](https://echarts.apache.org/en/api.html#echartsInstance.containPixel)
- `getDataURL` [](https://echarts.apache.org/en/api.html#echartsInstance.getDataURL)
- `getConnectedDataURL` [](https://echarts.apache.org/en/api.html#echartsInstance.getConnectedDataURL)
- `clear` [](https://echarts.apache.org/en/api.html#echartsInstance.clear)
- `dispose` [](https://echarts.apache.org/en/api.html#echartsInstance.dispose)
### Computed getters
> [!NOTE]
> The following ECharts instance methods aren't exposed because their functionality is already provided by component [props](#props):
>
> - [`showLoading`](https://echarts.apache.org/en/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/en/api.html#echartsInstance.hideLoading): use the `loading` and `loading-options` props instead.
> - `setTheme`: use the `theme` prop instead.
- Computed getters (`width`, `height`, `isDisposed` and `computedOptions`) are removed. Use the **`getWidth`, `getHeight`, `isDisposed` and `getOption`** methods instead.
### Slots
### Styles
Vue-ECharts allows you to define ECharts option's [`tooltip.formatter`](https://echarts.apache.org/en/option.html#tooltip.formatter) and [`toolbox.feature.dataView.optionToContent`](https://echarts.apache.org/en/option.html#toolbox.feature.dataView.optionToContent) callbacks via Vue slots instead of defining them in your `option` object. This simplifies custom HTMLElement rendering using familiar Vue templating.
- Now the root element of the component have **`100%×100%`** size by default, instead of `600×400`.
**Slot Naming Convention**
- Slot names begin with `tooltip`/`dataView`, followed by hyphen-separated path segments to the target.
- Each segment corresponds to an `option` property name or an array index (for arrays, use the numeric index).
- The constructed slot name maps directly to the nested callback it overrides.
**Example mappings**:
- `tooltip``option.tooltip.formatter`
- `tooltip-baseOption``option.baseOption.tooltip.formatter`
- `tooltip-xAxis-1``option.xAxis[1].tooltip.formatter`
- `tooltip-series-2-data-4``option.series[2].data[4].tooltip.formatter`
- `dataView``option.toolbox.feature.dataView.optionToContent`
- `dataView-media-1-option``option.media[1].option.toolbox.feature.dataView.optionToContent`
The slot props correspond to the first parameter of the callback function.
<details>
<summary>Usage</summary>
```vue
<template>
<v-chart :option="chartOptions">
<!-- Global `tooltip.formatter` -->
<template #tooltip="params">
<div v-for="(param, i) in params" :key="i">
<span v-html="param.marker" />
<span>{{ param.seriesName }}</span>
<span>{{ param.value[0] }}</span>
</div>
</template>
<!-- Tooltip on xAxis -->
<template #tooltip-xAxis="params">
<div>X-Axis : {{ params.value }}</div>
</template>
<!-- Data View Content -->
<template #dataView="option">
<table>
<thead>
<tr>
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
{{ t }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
<th>{{ row[0] }}</th>
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
</tr>
</tbody>
</table>
</template>
</v-chart>
</template>
```
[Example →](https://vue-echarts.dev/#line)
</details>
> [!NOTE]
> Slots take precedence over the corresponding callback defined in `props.option`.
### Static Methods
Static methods can be accessed from [`echarts` itself](https://echarts.apache.org/en/api.html#echarts).
## CSP: `style-src` or `style-src-elem`
If you are **both** enforcing a strict CSP that prevents inline `<style>` injection and targeting browsers that don't support the [CSSStyleSheet() constructor](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility), you need to manually include `vue-echarts/style.css`.
## Migration to v8
> [!NOTE]
> Please make sure to read the [upgrade guide](https://echarts.apache.org/handbook/en/basics/release-note/v6-upgrade-guide/) for ECharts 6 as well.
The following breaking changes are introduced in `vue-echarts@8`:
- **Vue 2 support is dropped:** If you still need to stay on Vue 2, use [`vue-echarts@7`](https://github.com/ecomfe/vue-echarts/tree/7.x).
- **Browser compatibility changes:** We no longer provide compatibility for browsers without native [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#browser_compatibility) support. If you need to support legacy browsers, you must transpile the code to ES5 yourself.
- **CSP entry point removed:** The entry point `vue-echarts/csp` is removed. Use `vue-echarts` instead. You only need to manually include `vue-echarts/style.css` if you are **both** enforcing a strict CSP that prevents inline `<style>` injection and targeting browsers that don't support the [`CSSStyleSheet()` constructor](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility).
## Local development
```bash
```sh
pnpm i
pnpm serve
pnpm dev
```
Open `http://localhost:8080` to see the demo.
Open `http://localhost:5173` to see the demo.
## Notice
The Apache Software Foundation [Apache ECharts, ECharts](https://echarts.apache.org/), Apache, the Apache feather, and the Apache ECharts project logo are either registered trademarks or trademarks of the [Apache Software Foundation](https://www.apache.org/).

View File

@ -1,51 +1,25 @@
<h1 align="center">Vue-ECharts</h1>
<p align="center">Apache ECharts <sup>(v5)</sup> 的 Vue.js <sup>(v2/v3)</sup> 组件。</p>
<p align="center"><a href="https://vue-echarts.dev/">查看 Demo →</a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"/></a> <a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"><img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"/></a></p>
> [!IMPORTANT]
> 我们新发布了一个[导入代码生成器](https://vue-echarts.dev/#codegen),只需要把`option` 代码粘贴进去,就可以得到精确的导入代码。
>
> ![](https://github.com/ecomfe/vue-echarts/assets/1726061/f9c38a06-3422-4f0e-ab8c-f242d9aea9aa)
>
> [试一试 →](https://vue-echarts.dev/#codegen)
<p align="center">Apache ECharts™ 的 Vue.js 组件。</p>
<p align="center"><a href="https://npmjs.com/package/vue-echarts"><img alt="npm 版本" src="https://img.shields.io/npm/v/vue-echarts"></a> <a href="https://vue-echarts.dev/"><img src="https://img.shields.io/badge/%E6%BC%94%E7%A4%BA%20%C2%BB-20c3aa" alt="查看演示"></a> <a href="./README.zh-Hans.md"></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"></a> <a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"><img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"></a></p>
---
## 💡 注意 💡
若您准备从 `vue-echarts` ≤ 5 的版本迁移到新版本,请在升级 v6 前阅读 _[迁移到 v6](#迁移到-v6)_ 部分文档。
没准备好的话,可以继续阅读老版本的文档。[前往 →](https://github.com/ecomfe/vue-echarts/blob/5.x/README.zh_CN.md)
> 还在使用 Vue 2可以继续阅读老版本的文档。[前往 →](https://github.com/ecomfe/vue-echarts/blob/7.x/README.zh-Hans.md)
## 安装 & 使用
### npm & ESM
### npm
```bash
```sh
npm install echarts vue-echarts
```
要在 _Vue 2_<2.7.0下使用 `vue-echarts`需要确保 `@vue/composition-api` 已经安装
```sh
npm i @vue/composition-api
```
如果你在使用基于 _Vue 2_ _NuxtJS_那么还需要安装 `@nuxtjs/composition-api`
```sh
npm i @nuxtjs/composition-api
```
然后在 `nuxt.config.js` `buildModules` 选项中添加 `'@nuxtjs/composition-api/module'`
#### 示例
<details>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-vue-3?file=src%2FApp.vue">Demo →</a></summary>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-8?file=src%2FApp.vue">Demo →</a></summary>
```vue
<template>
@ -59,7 +33,7 @@ import { PieChart } from "echarts/charts";
import {
TitleComponent,
TooltipComponent,
LegendComponent
LegendComponent,
} from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
import { ref, provide } from "vue";
@ -69,7 +43,7 @@ use([
PieChart,
TitleComponent,
TooltipComponent,
LegendComponent
LegendComponent,
]);
provide(THEME_KEY, "dark");
@ -77,16 +51,16 @@ provide(THEME_KEY, "dark");
const option = ref({
title: {
text: "Traffic Sources",
left: "center"
left: "center",
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
legend: {
orient: "vertical",
left: "left",
data: ["Direct", "Email", "Ad Networks", "Video Ads", "Search Engines"]
data: ["Direct", "Email", "Ad Networks", "Video Ads", "Search Engines"],
},
series: [
{
@ -99,17 +73,17 @@ const option = ref({
{ value: 310, name: "Email" },
{ value: 234, name: "Ad Networks" },
{ value: 135, name: "Video Ads" },
{ value: 1548, name: "Search Engines" }
{ value: 1548, name: "Search Engines" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)"
}
}
}
]
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
});
</script>
@ -122,103 +96,11 @@ const option = ref({
</details>
<details>
<summary>Vue 2 <a href="https://stackblitz.com/edit/vue-echarts-vue-2?file=src%2FApp.vue">Demo →</a></summary>
```vue
<template>
<v-chart class="chart" :option="option" />
</template>
<script>
import { use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { PieChart } from "echarts/charts";
import {
TitleComponent,
TooltipComponent,
LegendComponent
} from "echarts/components";
import VChart, { THEME_KEY } from "vue-echarts";
use([
CanvasRenderer,
PieChart,
TitleComponent,
TooltipComponent,
LegendComponent
]);
export default {
name: "HelloWorld",
components: {
VChart
},
provide: {
[THEME_KEY]: "dark"
},
data() {
return {
option: {
title: {
text: "Traffic Sources",
left: "center"
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: "vertical",
left: "left",
data: [
"Direct",
"Email",
"Ad Networks",
"Video Ads",
"Search Engines"
]
},
series: [
{
name: "Traffic Sources",
type: "pie",
radius: "55%",
center: ["50%", "60%"],
data: [
{ value: 335, name: "Direct" },
{ value: 310, name: "Email" },
{ value: 234, name: "Ad Networks" },
{ value: 135, name: "Video Ads" },
{ value: 1548, name: "Search Engines" }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)"
}
}
}
]
}
};
}
};
</script>
<style scoped>
.chart {
height: 400px;
}
</style>
```
</details>
> [!IMPORTANT]
> 我们鼓励手动从 ECharts 中引入组件和图表,以减小打包体积。我们已经为此构建了一个[导入代码生成器](https://vue-echarts.dev/#codegen)。你只需要把`option` 代码粘贴进去,就可以得到精确的导入代码。
>
> ![](https://github.com/ecomfe/vue-echarts/assets/1726061/f9c38a06-3422-4f0e-ab8c-f242d9aea9aa)
>
> [试一试 →](https://vue-echarts.dev/#codegen)
但如果你实在需要全量引入 ECharts 从而无需手动引入模块,只需要在代码中添加:
@ -227,19 +109,21 @@ export default {
import "echarts";
```
### CDN & 全局变量
### CDN
用如下方式在 HTML 中插入 `<script>` 标签,并且通过 `window.VueECharts` 来访问组件接口:
<details>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-vue-3-global?file=index.html">Demo →</a></summary>
<summary>Vue 3 <a href="https://stackblitz.com/edit/vue-echarts-8-global?file=index.html">Demo →</a></summary>
<!-- vue3Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.7"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.1"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.18"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@8.0.0-beta.1"></script>
```
<!-- vue3Scripts:end -->
```js
@ -251,25 +135,7 @@ app.component('v-chart', VueECharts)
</details>
<details>
<summary>Vue 2 <a href="https://stackblitz.com/edit/vue-echarts-vue-2-global?file=index.html">Demo →</a></summary>
<!-- vue2Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.15"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.1"></script>
```
<!-- vue2Scripts:end -->
```js
// 全局注册组件(也可以使用局部注册)
Vue.component("v-chart", VueECharts);
```
</details>
可以在[这里](https://github.com/ecomfe/vue-echarts/tree/main/src/demo)查看更多例子。
可以在[这里](https://github.com/ecomfe/vue-echarts/tree/main/demo)查看更多例子。
### Prop
@ -289,7 +155,8 @@ Vue.component("v-chart", VueECharts);
ECharts 的万能接口。修改这个 prop 会触发 ECharts 实例的 `setOption` 方法。查看[详情 →](https://echarts.apache.org/zh/option.html)
> 💡 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`。
> [!TIP]
> 在没有指定 `update-options` 时,如果直接修改 `option` 对象而引用保持不变,`setOption` 方法调用时将默认指定 `notMerge: false`;否则,如果为 `option` 绑定一个新的引用,将指定 `notMerge: true`。
- `update-options: object`
@ -329,8 +196,7 @@ Vue.component("v-chart", VueECharts);
</template>
```
> **Note**
>
> [!NOTE]
> 仅支持 `.once` 修饰符,因为其它修饰符都与 DOM 事件机制强耦合。
Vue-ECharts 支持如下事件:
@ -381,64 +247,69 @@ Vue-ECharts 支持如下事件:
请参考支持的事件列表。[前往 →](https://echarts.apache.org/zh/api.html#events)
#### 原生 DOM 事件
由于 Vue-ECharts 默认将事件绑定到 ECharts 实例,因此在使用原生 DOM 事件时需要做一些特殊处理。你需要在事件名称前加上 `native:` 前缀来绑定原生 DOM 事件。
```vue
<template>
<v-chart @native:click="handleClick" />
</template>
```
### Provide / Inject
Vue-ECharts 为 `theme``init-options``update-options``loading-options` 提供了 provide/inject API以通过上下文配置选项。例如可以通过如下方式来使用 provide API 为 `init-options` 提供上下文配置:
Vue-ECharts 为 `theme``init-options``update-options``loading-options` 提供了 provide/inject API以通过上下文配置选项。例如可以通过如下方式来使用 provide API 为 `theme` 提供上下文配置:
<details>
<summary>Vue 3</summary>
<summary>组合式 API</summary>
```js
import { THEME_KEY } from 'vue-echarts'
import { provide } from 'vue'
import { THEME_KEY } from "vue-echarts";
import { provide } from "vue";
// 组合式 API
provide(THEME_KEY, 'dark')
provide(THEME_KEY, "dark");
// 选项式 API
{
provide: {
[THEME_KEY]: 'dark'
}
}
// 或者 provide 一个 ref
const theme = ref("dark");
provide(THEME_KEY, theme);
// 也支持 getter
provide(THEME_KEY, () => theme.value);
```
</details>
<details>
<summary>Vue 2</summary>
<summary>选项式 API</summary>
```js
import { THEME_KEY } from 'vue-echarts'
import { computed } from 'vue'
// 组件选项中
{
provide: {
[THEME_KEY]: 'dark'
export default {
{
provide: {
[THEME_KEY]: 'dark'
}
}
}
// 或者让注入项具有响应性
export default {
data() {
return {
theme: 'dark'
}
},
provide() {
return {
[THEME_KEY]: computed(() => this.theme)
}
}
}
```
> **Note**
>
> 在 Vue 2 中,如果你想动态地改变这些选项,那么你需要提供一个对象。
>
> ```js
> // 组件选项中
> {
> data () {
> return {
> theme: { value: 'dark' }
> }
> },
> provide () {
> return {
> [THEME_KEY]: this.theme
> }
> }
> }
> ```
</details>
### 方法
@ -453,57 +324,117 @@ import { THEME_KEY } from 'vue-echarts'
- `convertToPixel` [](https://echarts.apache.org/zh/api.html#echartsInstance.convertToPixel)
- `convertFromPixel` [](https://echarts.apache.org/zh/api.html#echartsInstance.convertFromPixel)
- `containPixel` [](https://echarts.apache.org/zh/api.html#echartsInstance.containPixel)
- `showLoading` [](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading)
- `hideLoading` [](https://echarts.apache.org/zh/api.html#echartsInstance.hideLoading)
- `getDataURL` [](https://echarts.apache.org/zh/api.html#echartsInstance.getDataURL)
- `getConnectedDataURL` [](https://echarts.apache.org/zh/api.html#echartsInstance.getConnectedDataURL)
- `clear` [](https://echarts.apache.org/zh/api.html#echartsInstance.clear)
- `dispose` [](https://echarts.apache.org/zh/api.html#echartsInstance.dispose)
> [!NOTE]
> 如下 ECharts 实例方法没有被暴露,因为它们的功能已经通过组件 [props](#props) 提供了:
>
> - [`showLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.showLoading) / [`hideLoading`](https://echarts.apache.org/zh/api.html#echartsInstance.hideLoading):请使用 `loading` 和 `loading-options` prop。
> - `setTheme`:请使用 `theme` prop。
### 插槽Slots
Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.formatter`](https://echarts.apache.org/zh/option.html#tooltip.formatter) 和 [`toolbox.feature.dataView.optionToContent`](https://echarts.apache.org/zh/option.html#toolbox.feature.dataView.optionToContent) 回调,而无需在 `option` 对象中定义它们。你可以使用熟悉的 Vue 模板语法来编写自定义提示框或数据视图中的内容。
**插槽命名约定**
- 插槽名称以 `tooltip`/`dataView` 开头,后面跟随用连字符分隔的路径片段,用于定位目标。
- 每个路径片段对应 `option` 对象的属性名或数组索引(数组索引使用数字形式)。
- 拼接后的插槽名称直接映射到要覆盖的嵌套回调函数。
**示例映射**
- `tooltip``option.tooltip.formatter`
- `tooltip-baseOption``option.baseOption.tooltip.formatter`
- `tooltip-xAxis-1``option.xAxis[1].tooltip.formatter`
- `tooltip-series-2-data-4``option.series[2].data[4].tooltip.formatter`
- `dataView``option.toolbox.feature.dataView.optionToContent`
- `dataView-media-1-option``option.media[1].option.toolbox.feature.dataView.optionToContent`
插槽的 props 对象对应回调函数的第一个参数。
<details>
<summary>用法示例</summary>
```vue
<template>
<v-chart :option="chartOptions">
<!-- 全局 `tooltip.formatter` -->
<template #tooltip="params">
<div v-for="(param, i) in params" :key="i">
<span v-html="param.marker" />
<span>{{ param.seriesName }}</span>
<span>{{ param.value[0] }}</span>
</div>
</template>
<!-- x轴 tooltip -->
<template #tooltip-xAxis="params">
<div>X轴: {{ params.value }}</div>
</template>
<!-- 数据视图内容 -->
<template #dataView="option">
<table>
<thead>
<tr>
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
{{ t }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
<th>{{ row[0] }}</th>
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
</tr>
</tbody>
</table>
</template>
</v-chart>
</template>
```
[示例 →](https://vue-echarts.dev/#line)
</details>
> [!NOTE]
> 插槽会优先于 `props.option` 中对应的回调函数。
### 静态方法
静态方法请直接通过 [`echarts` 本身](https://echarts.apache.org/zh/api.html#echarts)进行调用。
## CSP: `style-src` 或 `style-src-elem`
如果你正在应用 CSP 来防止内联 `<style>` 注入,则需要使用 `dist/csp` 目录中的文件,并手动引入 `dist/csp/style.css`
如果你执行严格的 CSP 策略来防止内联 `<style>` 注入,**并且**需要兼容不支持 [CSSStyleSheet() 构造函数](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility) 的浏览器,则需要手动引入 `vue-echarts/style.css`
## 迁移到 v6
## 迁移到 v8
> 💡 请确保同时查阅 ECharts 5 的[升级指南](https://echarts.apache.org/zh/tutorial.html#ECharts%205%20%E5%8D%87%E7%BA%A7%E6%8C%87%E5%8D%97)。
> [!NOTE]
> 请确保同时查阅 [ECharts 6 的升级指南](https://echarts.apache.org/handbook/zh/basics/release-note/v6-upgrade-guide/)。
`vue-echarts@6` 引入了下破坏性变更:
`vue-echarts@8` 引入了下破坏性变更:
### Vue 2 支持
- **Vue 2 支持已移除:** 如果你仍需要继续使用 Vue 2请使用 [`vue-echarts@7`](https://github.com/ecomfe/vue-echarts/tree/7.x)。
- 要在 `vue@2.7.0` 之前的版本中使用 Vue-ECharts必须安装 `@vue/composition-api`
- **浏览器兼容性变更:** 我们不再为不支持原生 [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#browser_compatibility) 的浏览器提供兼容性支持。如果你需要支持旧版浏览器,必须自行将代码转译为 ES5
### Prop
- `options` 重命名为 **`option`**,以和 ECharts 本身保持一致。
- 更新 `option` 将采用 **`update-options`** 中的配置,不再检查是否发生引用变化。
- `watch-shallow` 被移除。在性能关键场景请使用 **`manual-update`**。
### 方法
- `mergeOptions` 重命名为 **`setOption`**,以和 ECharts 本身保持一致。
- `showLoading``hideLoading` 被移除。请使用 **`loading``loading-options`** prop。
- `appendData` 被移除。(由于 ECharts 5 引入的破坏性变更。)
- 所有静态方法被从 `vue-echarts` 移除。可以直接使用 `echarts` 本身的这些方法。
### 计算 Getter
- 计算 getter`width``height``isDisposed``computedOptions`)被移除。请分别使用 **`getWidth``getHeight``isDisposed``getOption`** 方法代替。
### 样式
- 现在组件根元素尺寸默认为 **`100%×100%`**,而非原来的 `600×400`
- **CSP 入口点已移除:** 入口点 `vue-echarts/csp` 已被移除。请使用 `vue-echarts` 替代。如果你执行严格的 CSP 策略来防止内联 `<style>` 注入,**并且**需要兼容不支持 [`CSSStyleSheet()` 构造函数](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility) 的浏览器,则需要手动引入 `vue-echarts/style.css`
## 本地开发
```bash
```sh
pnpm i
pnpm serve
pnpm dev
```
打开 `http://localhost:8080` 来查看 demo。
打开 `http://localhost:5173` 来查看 demo。
## 声明
The Apache Software Foundation [Apache ECharts, ECharts](https://echarts.apache.org/), Apache, the Apache feather, and the Apache ECharts project logo are either registered trademarks or trademarks of the [Apache Software Foundation](https://www.apache.org/).

View File

@ -1,3 +0,0 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

View File

@ -4,10 +4,8 @@ import {
computed,
watch,
onBeforeUnmount,
defineProps,
defineEmits,
onMounted,
nextTick
nextTick,
} from "vue";
import { useLocalStorage } from "@vueuse/core";
import "highlight.js/styles/github.css";
@ -16,6 +14,7 @@ import javascript from "highlight.js/lib/languages/javascript";
import typescript from "highlight.js/lib/languages/typescript";
import hljsVuePlugin from "@highlightjs/vue-plugin";
import { initialize, transform } from "esbuild-wasm";
import wasmURL from "esbuild-wasm/esbuild.wasm?url";
import { track } from "@vercel/analytics";
import { getImportsFromOption } from "./utils/codegen";
@ -30,7 +29,7 @@ const codegenOptions = useLocalStorage("ve.codegenOptions", {
multiline: false,
maxLen: 80,
semi: false,
includeType: false
includeType: false,
});
const props = defineProps({ open: Boolean, renderer: String });
@ -39,6 +38,10 @@ const emit = defineEmits(["update:open"]);
const dialog = ref(null);
let clickFrom = null;
function onMousedown(e) {
clickFrom = e.target;
}
function closeFromOutside() {
if (dialog.value?.contains(clickFrom)) {
return;
@ -54,7 +57,7 @@ const renderer = ref(props.renderer);
const source = ref(null);
watch(
() => props.open,
async val => {
async (val) => {
if (val) {
renderer.value = props.renderer;
}
@ -65,7 +68,7 @@ watch(
return;
}
source.value?.focus();
}
},
);
const copied = ref(false);
@ -75,9 +78,11 @@ const transformedCode = ref("");
const transformErrors = ref([]);
onMounted(async () => {
await initialize({
wasmURL: "https://cdn.jsdelivr.net/npm/esbuild-wasm@0.19.2/esbuild.wasm"
});
// prevent multiple initializations during HMR
if (!window.__esbuildInitialized) {
await initialize({ wasmURL });
window.__esbuildInitialized = true;
}
initializing.value = false;
@ -122,7 +127,7 @@ onMounted(async () => {
source.value?.focus();
});
watch(optionCode, async val => {
watch(optionCode, async (val) => {
try {
transformedCode.value = await transform(`(${val})`, { loader: "ts" });
transformErrors.value = [];
@ -163,7 +168,7 @@ const importCode = computed(() => {
try {
return getImportsFromOption(eval(transformedCode.value.code), {
renderer: renderer.value,
...codegenOptions.value
...codegenOptions.value,
});
} catch (e) {
return `/* Invalid ECharts option */
@ -212,7 +217,7 @@ onBeforeUnmount(() => {
<aside
class="modal"
:class="{ open: props.open }"
@mousedown="clickFrom = $event.target"
@mousedown="onMousedown"
@click="closeFromOutside"
@keydown.esc="close"
>
@ -324,10 +329,6 @@ input[type="number"] {
overflow: hidden;
background-color: #fff;
box-shadow: 0 0 45px rgba(0, 0, 0, 0.2);
h2 {
margin-top: 0;
}
}
.options {
@ -418,7 +419,9 @@ input[type="number"] {
transform: translate(-50%, 200%);
border-radius: 4px;
opacity: 0;
transition: transform 0.2s, opacity 0.2s;
transition:
transform 0.2s,
opacity 0.2s;
}
.message.open {

View File

@ -3,27 +3,28 @@ import { provide, computed, ref, watch } from "vue";
import { useUrlSearchParams } from "@vueuse/core";
import { use } from "echarts/core";
import { CanvasRenderer, SVGRenderer } from "echarts/renderers";
import { INIT_OPTIONS_KEY } from "../ECharts";
import { INIT_OPTIONS_KEY } from "../src/ECharts";
import { track } from "@vercel/analytics";
import LogoChart from "./examples/LogoChart";
import BarChart from "./examples/BarChart";
import PieChart from "./examples/PieChart";
import PolarChart from "./examples/PolarChart";
import ScatterChart from "./examples/ScatterChart";
import GeoChart from "./examples/GeoChart";
import RadarChart from "./examples/RadarChart";
import ConnectChart from "./examples/ConnectChart";
import GlChart from "./examples/GlChart";
import ManualChart from "./examples/ManualChart";
import LogoChart from "./examples/LogoChart.vue";
import BarChart from "./examples/BarChart.vue";
import LineChart from "./examples/LineChart.vue";
import PieChart from "./examples/PieChart.vue";
import PolarChart from "./examples/PolarChart.vue";
import ScatterChart from "./examples/ScatterChart.vue";
import GeoChart from "./examples/GeoChart.vue";
import RadarChart from "./examples/RadarChart.vue";
import ConnectChart from "./examples/ConnectChart.vue";
import GlChart from "./examples/GlChart.vue";
import ManualChart from "./examples/ManualChart.vue";
import CodeGen from "./CodeGen";
import CodeGen from "./CodeGen.vue";
use([CanvasRenderer, SVGRenderer]);
const params = useUrlSearchParams();
const initOptions = computed(() => ({
renderer: params.renderer || "canvas"
renderer: params.renderer || "canvas",
}));
provide(INIT_OPTIONS_KEY, initOptions);
@ -39,7 +40,7 @@ function openCodegen() {
track("codegen", { from: "click" });
}
watch(codeOpen, open => {
watch(codeOpen, (open) => {
if (open) {
location.hash = "#codegen";
} else {
@ -56,13 +57,25 @@ watch(codeOpen, open => {
<a href="https://github.com/ecomfe/vue-echarts">Vue-ECharts</a>
</h1>
<p class="desc">
Vue.js component for Apache ECharts. (<a
Vue.js component for Apache ECharts. (<a
href="https://github.com/ecomfe/vue-echarts#readme"
>docs</a
>)
</p>
<h2 class="sep">Examples</h2>
<p>
<small
>See
<a href="https://echarts.apache.org/examples/en/index.html"
>echarts.apache.org/examples</a
>
for all examples.</small
>
</p>
<bar-chart />
<line-chart />
<pie-chart />
<polar-chart />
<scatter-chart />
@ -83,7 +96,7 @@ watch(codeOpen, open => {
<aside class="renderer">
<button
:class="{
active: initOptions.renderer === 'canvas'
active: initOptions.renderer === 'canvas',
}"
@click="params.renderer = 'canvas'"
>
@ -91,7 +104,7 @@ watch(codeOpen, open => {
</button>
<button
:class="{
active: initOptions.renderer === 'svg'
active: initOptions.renderer === 'svg',
}"
@click="params.renderer = 'svg'"
>
@ -100,9 +113,7 @@ watch(codeOpen, open => {
</aside>
<aside class="codegen">
<button @click="openCodegen">
<code>import</code> Codegen <span class="badge">beta</span>
</button>
<button @click="openCodegen"> <code>import</code> Codegen</button>
</aside>
<code-gen v-model:open="codeOpen" :renderer="initOptions.renderer" />
@ -132,6 +143,12 @@ body {
a {
color: inherit;
text-decoration: none;
box-shadow: 0 1px 0 0 #42b983;
transition: box-shadow 0.2s;
&:hover {
box-shadow: 0 2px 0 0 #42b983;
}
}
h1 {
@ -139,13 +156,32 @@ h1 {
font-family: Inter, "Helvetica Neue", Arial, sans-serif;
}
h1,
h2 {
color: #2c3e50;
font-weight: 400;
margin-top: 1em;
margin-bottom: 1em;
}
h2 {
.sep {
margin-top: 6em;
margin-bottom: 1.8em;
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
font-size: 1.25em;
color: #7f8c8d;
opacity: 0.6;
&::before,
&::after {
content: "";
display: block;
width: 48px;
border-bottom: 1px dotted currentColor;
}
}
h3 {
margin-top: 2em;
padding-top: 1em;
font-size: 1.2em;
@ -196,8 +232,9 @@ pre {
pre,
code,
textarea {
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
"Liberation Mono", monospace;
font-family:
ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono",
monospace;
}
footer {
@ -208,15 +245,20 @@ footer {
a {
display: inline-block;
margin: 0 5px;
padding: 3px 0 6px;
color: #7f8c8d;
font-size: 2em;
text-decoration: none;
}
}
h1,
h2,
h3 {
color: #2c3e50;
font-weight: 400;
a,
a:hover {
padding-bottom: 3px;
border-bottom: 3px solid #42b983;
text-decoration: none;
box-shadow: none;
}
}
@ -251,7 +293,7 @@ input {
}
label {
display: flex;
display: inline-flex;
align-items: center;
justify-content: center;
}
@ -265,7 +307,7 @@ select {
}
#logo {
display: inline-block;
display: inline-flex;
width: 128px;
height: 128px;
pointer-events: none;
@ -335,11 +377,13 @@ select {
}
}
}
}
.actions {
display: flex;
justify-content: center;
}
.actions {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.renderer,
@ -399,15 +443,6 @@ select {
align-items: center;
padding: 0 4px;
gap: 4px;
.badge {
display: block;
padding: 2px 3px;
font-size: 10px;
background: #36485e54;
color: #fff;
border-radius: 4px;
}
}
}

View File

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 465 750">
<path fill="currentColor" d="M367.855,428.202c-3.674-1.385-7.452-1.966-11.146-1.794c0.659-2.922,0.844-5.85,0.58-8.719 c-0.937-10.407-7.663-19.864-18.063-23.834c-10.697-4.043-22.298-1.168-29.902,6.403c3.015,0.026,6.074,0.594,9.035,1.728 c13.626,5.151,20.465,20.379,15.32,34.004c-1.905,5.02-5.177,9.115-9.22,12.05c-6.951,4.992-16.19,6.536-24.777,3.271 c-13.625-5.137-20.471-20.371-15.32-34.004c0.673-1.768,1.523-3.423,2.526-4.992h-0.014c0,0,0,0,0,0.014 c4.386-6.853,8.145-14.279,11.146-22.187c23.294-61.505-7.689-130.278-69.215-153.579c-61.532-23.293-130.279,7.69-153.579,69.202 c-6.371,16.785-8.679,34.097-7.426,50.901c0.026,0.554,0.079,1.121,0.132,1.688c4.973,57.107,41.767,109.148,98.945,130.793 c58.162,22.008,121.303,6.529,162.839-34.465c7.103-6.893,17.826-9.444,27.679-5.719c11.858,4.491,18.565,16.6,16.719,28.643 c4.438-3.126,8.033-7.564,10.117-13.045C389.751,449.992,382.411,433.709,367.855,428.202z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 465 750">
<path fill="currentColor" d="M367.855,428.202c-3.674-1.385-7.452-1.966-11.146-1.794c0.659-2.922,0.844-5.85,0.58-8.719 c-0.937-10.407-7.663-19.864-18.063-23.834c-10.697-4.043-22.298-1.168-29.902,6.403c3.015,0.026,6.074,0.594,9.035,1.728 c13.626,5.151,20.465,20.379,15.32,34.004c-1.905,5.02-5.177,9.115-9.22,12.05c-6.951,4.992-16.19,6.536-24.777,3.271 c-13.625-5.137-20.471-20.371-15.32-34.004c0.673-1.768,1.523-3.423,2.526-4.992h-0.014c0,0,0,0,0,0.014 c4.386-6.853,8.145-14.279,11.146-22.187c23.294-61.505-7.689-130.278-69.215-153.579c-61.532-23.293-130.279,7.69-153.579,69.202 c-6.371,16.785-8.679,34.097-7.426,50.901c0.026,0.554,0.079,1.121,0.132,1.688c4.973,57.107,41.767,109.148,98.945,130.793 c58.162,22.008,121.303,6.529,162.839-34.465c7.103-6.893,17.826-9.444,27.679-5.719c11.858,4.491,18.565,16.6,16.719,28.643 c4.438-3.126,8.033-7.564,10.117-13.045C389.751,449.992,382.411,433.709,367.855,428.202z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1011 B

After

Width:  |  Height:  |  Size: 1008 B

View File

Before

Width:  |  Height:  |  Size: 901 KiB

After

Width:  |  Height:  |  Size: 901 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -6,7 +6,7 @@ export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
dataset: {
dimensions: ["Product", "2015", "2016", "2017"],
@ -15,32 +15,32 @@ export default function getData() {
Product: "Matcha Latte",
2015: random(),
2016: random(),
2017: random()
2017: random(),
},
{
Product: "Milk Tea",
2015: random(),
2016: random(),
2017: random()
2017: random(),
},
{
Product: "Cheese Cocoa",
2015: random(),
2016: random(),
2017: random()
2017: random(),
},
{
Product: "Walnut Brownie",
2015: random(),
2016: random(),
2017: random()
}
]
2017: random(),
},
],
},
xAxis: { type: "category" },
yAxis: {},
// Declare several bar series, each will be mapped
// to a column of dataset.source by default.
series: [{ type: "bar" }, { type: "bar" }, { type: "bar" }]
series: [{ type: "bar" }, { type: "bar" }, { type: "bar" }],
};
}

View File

@ -5,42 +5,44 @@ for (let i = 0; i < 16; i++) {
Math.random() * 5,
Math.random() * 4,
Math.random() * 12,
Math.round(Math.random() * (symbolCount - 1))
Math.round(Math.random() * (symbolCount - 1)),
]);
}
const c1 = {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
legend: {
top: 20,
data: ["scatter"]
top: "3%",
data: ["scatter"],
},
tooltip: {
formatter: "{c}"
formatter: "{c}",
},
grid: {
top: "26%",
bottom: "26%"
top: "30%",
right: "18%",
bottom: "20%",
},
xAxis: {
type: "value",
splitLine: {
show: false
}
show: false,
},
},
yAxis: {
type: "value",
splitLine: {
show: false
}
show: false,
},
},
visualMap: [
{
realtime: false,
left: "right",
right: "2%",
bottom: "3%",
selectedMode: "multiple",
dimension: 2,
selected: [],
@ -48,50 +50,52 @@ const c1 = {
max: 18,
precision: 0,
splitNumber: 0,
calculable: true
}
calculable: true,
},
],
series: [
{
name: "scatter",
type: "scatter",
symbolSize: 30,
data: data1
}
]
data: data1,
},
],
};
const c2 = {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
legend: {
top: 20,
data: ["scatter"]
top: "3%",
data: ["scatter"],
},
tooltip: {
formatter: "{c}"
formatter: "{c}",
},
grid: {
top: "26%",
bottom: "26%"
top: "30%",
right: "18%",
bottom: "20%",
},
xAxis: {
type: "value",
splitLine: {
show: false
}
show: false,
},
},
yAxis: {
type: "value",
splitLine: {
show: false
}
show: false,
},
},
visualMap: [
{
left: "right",
right: "2%",
bottom: "3%",
selectedMode: "multiple",
dimension: 2,
selected: [],
@ -99,17 +103,17 @@ const c2 = {
max: 18,
precision: 0,
splitNumber: 0,
calculable: true
}
calculable: true,
},
],
series: [
{
name: "scatter",
type: "scatter",
symbolSize: 30,
data: data1
}
]
data: data1,
},
],
};
export default function getData() {

56
demo/data/line.js Normal file
View File

@ -0,0 +1,56 @@
export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300,
},
legend: { top: 20 },
tooltip: {
trigger: "axis",
},
dataset: {
source: [
["product", "2012", "2013", "2014", "2015", "2016", "2017"],
["Milk Tea", 56.5, 82.1, 88.7, 70.1, 53.4, 85.1],
["Matcha Latte", 51.1, 51.4, 55.1, 53.3, 73.8, 68.7],
["Cheese Cocoa", 40.1, 62.2, 69.5, 36.4, 45.2, 32.5],
["Walnut Brownie", 25.2, 37.1, 41.2, 18, 33.9, 49.1],
],
},
xAxis: {
type: "category",
triggerEvent: true,
tooltip: { show: true, formatter: "" },
},
yAxis: {
triggerEvent: true,
tooltip: { show: true, formatter: "" },
},
series: [
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
{
type: "line",
smooth: true,
seriesLayoutBy: "row",
emphasis: { focus: "series" },
},
],
};
}

View File

@ -1,4 +1,4 @@
import logo from "../assets/Vue-ECharts.svg";
import logo from "../assets/Vue-ECharts.svg?raw";
const d = logo.match(/\bd="([^"]+)"/)[1];
@ -9,23 +9,25 @@ export default {
data: [0.7, 0.6, 0.55, 0.45],
amplitude: 6,
outline: {
show: false
show: false,
},
radius: "60%",
color: ["#4fc08d", "#44d64a", "#33c762", "#4acc80"],
backgroundStyle: {
color: "#fff",
borderColor: "#2c3e50",
borderWidth: 1
borderWidth: 1,
},
shape: `path://${d}`,
label: {
normal: {
formatter() {
return "";
}
}
}
}
]
formatter() {
return "";
},
},
itemStyle: {
shadowBlur: 12,
shadowColor: "rgba(0, 0, 0, 0.25)",
},
},
],
};

View File

@ -188,7 +188,7 @@ const data = [
{ name: "菏泽", value: 194 },
{ name: "合肥", value: 229 },
{ name: "武汉", value: 273 },
{ name: "大庆", value: 279 }
{ name: "大庆", value: 279 },
];
const geoCoordMap = {
海门: [121.15, 31.89],
@ -380,7 +380,7 @@ const geoCoordMap = {
菏泽: [115.480656, 35.23375],
合肥: [117.27, 31.86],
武汉: [114.31, 30.52],
大庆: [125.03, 46.58]
大庆: [125.03, 46.58],
};
function convertData(data) {
@ -390,7 +390,7 @@ function convertData(data) {
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value)
value: geoCoord.concat(data[i].value),
});
}
}
@ -401,91 +401,94 @@ export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
backgroundColor: "#404a59",
title: {
text: "Air quality of major cities in China",
subtext: "data from PM25.in",
sublink: "http://www.pm25.in",
top: "5%",
left: "center",
textStyle: {
color: "#fff"
}
color: "#fff",
},
},
tooltip: {
trigger: "item"
trigger: "item",
},
legend: {
orient: "vertical",
y: "bottom",
x: "right",
data: ["pm2.5"],
right: "5%",
bottom: "5%",
data: ["PM2.5"],
textStyle: {
color: "#fff"
}
color: "#fff",
},
},
geo: {
map: "china",
emphasis: {
label: {
show: false
show: false,
},
itemStyle: {
areaColor: "#2a333d"
}
areaColor: "#2a333d",
},
},
itemStyle: {
areaColor: "#323c48",
borderColor: "#111"
}
borderColor: "#111",
},
top: "20%",
bottom: "7%",
},
series: [
{
name: "pm2.5",
name: "PM2.5",
type: "scatter",
coordinateSystem: "geo",
data: convertData(data),
symbolSize: val => val[2] / 10,
symbolSize: (val) => val[2] / 10,
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
},
itemStyle: {
color: "#ddb926"
}
color: "#ddb926",
},
},
{
name: "Top 5",
type: "effectScatter",
coordinateSystem: "geo",
data: convertData(data.sort((a, b) => b.value - a.value).slice(0, 6)),
symbolSize: val => val[2] / 10,
symbolSize: (val) => val[2] / 10,
showEffectOn: "render",
rippleEffect: {
brushType: "stroke"
brushType: "stroke",
},
emphasis: {
scale: true
scale: true,
},
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
},
label: {
formatter: "{b}",
position: "right",
show: true
show: true,
},
itemStyle: {
color: "#f4e925",
shadowBlur: 10,
shadowColor: "#333"
shadowColor: "#333",
},
zlevel: 1
}
]
zlevel: 1,
},
],
};
}

View File

@ -2,20 +2,22 @@ export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
title: {
text: "Traffic Sources",
left: "center"
top: "5%",
left: "center",
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
formatter: "{a} <br/>{b} : {c} ({d}%)",
},
legend: {
orient: "vertical",
left: "left",
data: ["Direct", "Email", "Ad Networks", "Video Ads", "Search Engines"]
top: "5%",
left: "5%",
data: ["Direct", "Email", "Ad Networks", "Video Ads", "Search Engines"],
},
series: [
{
@ -28,16 +30,16 @@ export default function getData() {
{ value: 310, name: "Email" },
{ value: 234, name: "Ad Networks" },
{ value: 135, name: "Video Ads" },
{ value: 1548, name: "Search Engines" }
{ value: 1548, name: "Search Engines" },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)"
}
}
}
]
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
},
],
};
}

View File

@ -10,29 +10,33 @@ export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
title: {
text: "Dual Numeric Axis"
text: "Dual Numeric Axis",
top: "5%",
left: "5%",
},
legend: {
data: ["line"]
data: ["line"],
top: "6%",
},
polar: {
center: ["50%", "54%"]
radius: "65%",
center: ["50%", "56%"],
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross"
}
type: "cross",
},
},
angleAxis: {
type: "value",
startAngle: 0
startAngle: 0,
},
radiusAxis: {
min: 0
min: 0,
},
series: [
{
@ -40,9 +44,9 @@ export default function getData() {
name: "line",
type: "line",
showSymbol: false,
data: data
}
data: data,
},
],
animationDuration: 2000
animationDuration: 2000,
};
}

View File

@ -8,59 +8,40 @@ export const useScoreStore = defineStore("store", () => {
{ name: "Speed", max: 20, value: 18 },
{ name: "Strength", max: 20, value: 16 },
{ name: "Endurance", max: 20, value: 16 },
{ name: "Agility", max: 20, value: 20 }
{ name: "Agility", max: 20, value: 20 },
]);
const metrics = computed(() => {
return scores.value.map(({ name }) => name);
});
const scoreRadar = computed(() => {
function getRadarData(activeIndex: number) {
return {
title: {
text: "Player Ability"
text: "Player Ability",
top: "5%",
left: "5%",
},
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
radar: {
indicator: scores.value.map(({ name, max }) => {
return { name, max };
})
},
series: [
{
name: "Value",
type: "radar",
data: [{ value: scores.value.map(({ value }) => value) }]
}
]
};
});
function getRadarData(activeIndex: number) {
return {
animation: false,
title: {
text: "Player Ability"
},
tooltip: {},
radar: {
splitNumber: 4,
indicator: scores.value.map(({ name, max }, index) => {
if (index === activeIndex) {
return { name, max, color: "goldenrod" };
}
return { name, max };
})
}),
},
series: [
{
name: "Value",
type: "radar",
data: [{ value: scores.value.map(({ value }) => value) }]
}
]
data: [{ value: scores.value.map(({ value }) => value) }],
},
],
};
}
@ -81,10 +62,9 @@ export const useScoreStore = defineStore("store", () => {
return {
scores,
metrics,
scoreRadar,
getRadarData,
increase,
isMax,
isMin
isMin,
};
});

View File

@ -20,7 +20,7 @@ const data = [
[19349, 69.6, 147568552, "Russia", 1990],
[10670, 67.3, 53994605, "Turkey", 1990],
[26424, 75.7, 57110117, "United Kingdom", 1990],
[37062, 75.4, 252847810, "United States", 1990]
[37062, 75.4, 252847810, "United States", 1990],
],
[
[44056, 81.8, 23968973, "Australia", 2015],
@ -41,37 +41,43 @@ const data = [
[23038, 73.13, 143456918, "Russia", 2015],
[19360, 76.5, 78665830, "Turkey", 2015],
[38225, 81.4, 64715810, "United Kingdom", 2015],
[53354, 79.1, 321773631, "United States", 2015]
]
[53354, 79.1, 321773631, "United States", 2015],
],
];
export default function getData() {
return {
grid: {
top: "25%",
},
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
fontWeight: 300,
},
title: {
text: "Life Expectancy vs. GDP by country"
text: "Life Expectancy vs. GDP by country",
top: "5%",
left: "5%",
},
legend: {
right: 10,
data: ["1990", "2015"]
top: "6%",
right: "5%",
data: ["1990", "2015"],
},
xAxis: {
splitLine: {
lineStyle: {
type: "dashed"
}
}
type: "dashed",
},
},
},
yAxis: {
splitLine: {
lineStyle: {
type: "dashed"
}
type: "dashed",
},
},
scale: true
scale: true,
},
series: [
{
@ -87,8 +93,8 @@ export default function getData() {
formatter({ data }) {
return data[3];
},
position: "top"
}
position: "top",
},
},
itemStyle: {
shadowBlur: 10,
@ -97,14 +103,14 @@ export default function getData() {
color: new graphic.RadialGradient(0.4, 0.3, 1, [
{
offset: 0,
color: "rgb(251, 118, 123)"
color: "rgb(251, 118, 123)",
},
{
offset: 1,
color: "rgb(204, 46, 72)"
}
])
}
color: "rgb(204, 46, 72)",
},
]),
},
},
{
name: "2015",
@ -119,8 +125,8 @@ export default function getData() {
formatter({ data }) {
return data[3];
},
position: "top"
}
position: "top",
},
},
itemStyle: {
shadowBlur: 10,
@ -129,15 +135,15 @@ export default function getData() {
color: new graphic.RadialGradient(0.4, 0.3, 1, [
{
offset: 0,
color: "rgb(129, 227, 238)"
color: "rgb(129, 227, 238)",
},
{
offset: 1,
color: "rgb(25, 183, 207)"
}
])
}
}
]
color: "rgb(25, 183, 207)",
},
]),
},
},
],
};
}

View File

@ -3,8 +3,8 @@ import { use, registerTheme } from "echarts/core";
import { BarChart } from "echarts/charts";
import { GridComponent, DatasetComponent } from "echarts/components";
import { shallowRef, onBeforeUnmount } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/bar";
import theme from "../theme.json";
@ -16,7 +16,7 @@ const loading = shallowRef(false);
const loadingOptions = {
text: "Loading…",
color: "#4ea397",
maskColor: "rgba(255, 255, 255, 0.4)"
maskColor: "rgba(255, 255, 255, 0.4)",
};
const option = shallowRef(getData());

View File

@ -5,11 +5,11 @@ import {
GridComponent,
TitleComponent,
VisualMapComponent,
TooltipComponent
TooltipComponent,
} from "echarts/components";
import { shallowRef, watch } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/connect";
use([
@ -17,7 +17,7 @@ use([
GridComponent,
TitleComponent,
VisualMapComponent,
TooltipComponent
TooltipComponent,
]);
const [c1, c2] = getData().map(shallowRef);
@ -25,14 +25,14 @@ const connected = shallowRef(true);
watch(
connected,
value => {
(value) => {
if (value) {
connect("radiance");
} else {
disconnect("radiance");
}
},
{ immediate: true }
{ immediate: true },
);
</script>
@ -46,10 +46,8 @@ watch(
</template>
<template #extra>
<p class="actions">
<label>
<input type="checkbox" v-model="connected" />
Connected
</label>
<input id="connected-check" type="checkbox" v-model="connected" />
<label for="connected-check">Connected</label>
</p>
</template>
</v-example>

View File

@ -1,10 +1,10 @@
<template>
<h2 :id="id">
<h3 :id="id">
<a :href="`#${id}`">
{{ title }}
<small v-if="desc">{{ desc }}</small>
</a>
</h2>
</h3>
<section>
<figure class="fig hero" v-if="!split">
<slot />
@ -22,19 +22,17 @@
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
id: {
type: String,
required: true
required: true,
},
title: {
type: String,
required: true
required: true,
},
desc: String,
split: Boolean
split: Boolean,
});
</script>
@ -45,11 +43,10 @@ defineProps({
width: fit-content;
margin: 2em auto;
.echarts {
> .echarts {
width: calc(60vw + 4em);
height: 360px;
max-width: 720px;
padding: 1.5em 2em;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
box-shadow: 0 0 45px rgba(0, 0, 0, 0.2);
@ -75,7 +72,6 @@ defineProps({
width: 100%;
min-width: 0;
height: 60vw;
padding: 1em 0;
border: none;
border-radius: 0;
box-shadow: none;
@ -88,7 +84,6 @@ defineProps({
.echarts {
width: 28vw;
min-width: 240px;
padding: 1em 1.5em;
height: 180px;
}

View File

@ -5,13 +5,13 @@ import {
GeoComponent,
TitleComponent,
LegendComponent,
TooltipComponent
TooltipComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/map";
import chinaMap from "../china.json";
import chinaMap from "../data/china.json";
use([
ScatterChart,
@ -19,7 +19,7 @@ use([
GeoComponent,
TitleComponent,
LegendComponent,
TooltipComponent
TooltipComponent,
]);
registerMap("china", chinaMap);
@ -33,10 +33,10 @@ let img = null;
function convert() {
img = {
src: map.value.getDataURL({
pixelRatio: window.devicePixelRatio || 1
pixelRatio: window.devicePixelRatio || 1,
}),
width: map.value.getWidth(),
height: map.value.getHeight()
height: map.value.getHeight(),
};
open.value = true;
}

View File

@ -4,8 +4,8 @@ import { Bar3DChart } from "echarts-gl/charts";
import { VisualMapComponent } from "echarts/components";
import { GlobeComponent } from "echarts-gl/components";
import { shallowRef, onMounted } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import world from "../assets/world.jpg";
import starfield from "../assets/starfield.jpg";
@ -15,14 +15,14 @@ const option = shallowRef();
const loading = shallowRef(true);
const initOptions = {
renderer: "canvas"
renderer: "canvas",
};
const loadingOptions = {
text: "Loading...",
color: "#000",
textColor: "#fff",
maskColor: "transparent"
maskColor: "transparent",
};
onMounted(() => {
@ -30,8 +30,8 @@ onMounted(() => {
loading.value = false;
data = data
.filter(dataItem => dataItem[2] > 0)
.map(dataItem => [dataItem[0], dataItem[1], Math.sqrt(dataItem[2])]);
.filter((dataItem) => dataItem[2] > 0)
.map((dataItem) => [dataItem[0], dataItem[1], Math.sqrt(dataItem[2])]);
option.value = {
backgroundColor: "#000",
@ -42,31 +42,33 @@ onMounted(() => {
environment: starfield,
light: {
main: {
intensity: 2
}
intensity: 2,
},
},
viewControl: {
autoRotate: false
}
autoRotate: false,
},
},
visualMap: {
bottom: "3%",
left: "3%",
max: 40,
calculable: true,
realtime: false,
inRange: {
colorLightness: [0.2, 0.9]
colorLightness: [0.2, 0.9],
},
textStyle: {
color: "#fff"
color: "#fff",
},
controller: {
inRange: {
color: "orange"
}
color: "orange",
},
},
outOfRange: {
colorAlpha: 0
}
colorAlpha: 0,
},
},
series: [
{
@ -77,10 +79,10 @@ onMounted(() => {
minHeight: 0.2,
silent: true,
itemStyle: {
color: "orange"
}
}
]
color: "orange",
},
},
],
};
});
});

108
demo/examples/LineChart.vue Normal file
View File

@ -0,0 +1,108 @@
<script setup>
import { use } from "echarts/core";
import { LineChart, PieChart } from "echarts/charts";
import {
GridComponent,
DatasetComponent,
LegendComponent,
TooltipComponent,
ToolboxComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/line";
use([
DatasetComponent,
GridComponent,
LegendComponent,
LineChart,
TooltipComponent,
ToolboxComponent,
PieChart,
]);
const option = shallowRef(getData());
const axis = shallowRef("xAxis");
function getPieOption(params) {
const option = {
dataset: { source: [params[0].dimensionNames, params[0].data] },
series: [
{
type: "pie",
radius: ["60%", "100%"],
seriesLayoutBy: "row",
itemStyle: {
borderRadius: 5,
borderColor: "#fff",
borderWidth: 2,
},
label: {
position: "center",
formatter: params[0].name,
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300,
},
},
],
};
return option;
}
</script>
<template>
<v-example
id="line"
title="Line chart"
desc="(with tooltip and dataView slots)"
>
<v-chart :option="option" autoresize>
<template #tooltip="params">
<v-chart
:style="{ width: '100px', height: '100px' }"
:option="getPieOption(params)"
autoresize
/>
</template>
<template #[`tooltip-${axis}`]="params">
{{ axis === "xAxis" ? "Year" : "Value" }}:
<b>{{ params.name }}</b>
</template>
<template #dataView="option">
<table style="margin: 20px auto">
<thead>
<tr>
<th v-for="(t, i) in option.dataset[0].source[0]" :key="i">
{{ t }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in option.dataset[0].source.slice(1)" :key="i">
<th>{{ row[0] }}</th>
<td v-for="(v, i) in row.slice(1)" :key="i">{{ v }}</td>
</tr>
</tbody>
</table>
</template>
</v-chart>
<template #extra>
<p class="actions">
Custom tooltip on
<select v-model="axis">
<option value="xAxis">X Axis</option>
<option value="yAxis">Y Axis</option>
</select>
</p>
</template>
</v-example>
</template>
<style scoped>
th,
td {
padding: 4px 8px;
}
</style>

View File

@ -2,7 +2,7 @@
import { registerTheme } from "echarts/core";
import "echarts-liquidfill";
import VChart from "../../ECharts";
import VChart from "../../src/ECharts";
import theme from "../theme.json";
import logo from "../data/logo";

View File

@ -4,12 +4,12 @@ import { LinesChart } from "echarts/charts";
import {
GeoComponent,
TitleComponent,
TooltipComponent
TooltipComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import worldMap from "../world.json";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import worldMap from "../data/world.json";
use([LinesChart, GeoComponent, TitleComponent, TooltipComponent]);
registerMap("world", worldMap);
@ -23,7 +23,7 @@ const loadingOptions = {
color: "#c23531",
textColor: "rgba(255, 255, 255, 0.5)",
maskColor: "#003",
zlevel: 0
zlevel: 0,
};
function load() {
@ -36,20 +36,21 @@ function load() {
function getAirportCoord(idx) {
return [data.airports[idx][3], data.airports[idx][4]];
}
const routes = data.routes.map(airline => {
const routes = data.routes.map((airline) => {
return [getAirportCoord(airline[1]), getAirportCoord(airline[2])];
});
chart.value.setOption({
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
},
title: {
text: "World Flights",
top: "5%",
left: "center",
textStyle: {
color: "#eee"
}
color: "#eee",
},
},
backgroundColor: "#003",
tooltip: {
@ -58,33 +59,33 @@ function load() {
return (
data.airports[route[1]][1] + " > " + data.airports[route[2]][1]
);
}
},
},
geo: {
map: "world",
left: 0,
right: 0,
top: "15%",
right: "5%",
bottom: "5%",
left: "5%",
silent: true,
itemStyle: {
borderColor: "#003",
color: "#005"
}
color: "#005",
},
},
series: [
{
type: "lines",
coordinateSystem: "geo",
data: routes,
large: true,
largeThreshold: 100,
lineStyle: {
opacity: 0.05,
width: 0.5,
curveness: 0.3
curveness: 0.3,
},
blendMode: "lighter"
}
]
blendMode: "lighter",
},
],
});
});
}

View File

@ -5,11 +5,11 @@ import {
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
TooltipComponent,
} from "echarts/components";
import { shallowRef, onMounted } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import { shallowRef, onMounted, onUnmounted } from "vue";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/pie";
use([
@ -17,7 +17,7 @@ use([
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
TooltipComponent,
]);
const option = shallowRef(getData());
@ -27,10 +27,10 @@ let timer = null;
onMounted(() => {
startActions();
});
return () => {
stopActions();
};
onUnmounted(() => {
stopActions();
});
function startActions() {
@ -54,18 +54,18 @@ function startActions() {
pie.value.dispatchAction({
type: "downplay",
seriesIndex: 0,
dataIndex
dataIndex,
});
dataIndex = (dataIndex + 1) % dataLen;
pie.value.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex
dataIndex,
});
pie.value.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex
dataIndex,
});
}, 1000);
}

View File

@ -5,11 +5,11 @@ import {
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
TooltipComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import { computed, shallowRef } from "vue";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/polar";
use([
@ -17,11 +17,28 @@ use([
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
TooltipComponent,
]);
const option = shallowRef(getData());
const theme = shallowRef("dark");
const loading = shallowRef(false);
const loadingOptions = computed(() =>
theme.value === "dark"
? {
color: "#fff",
textColor: "#fff",
maskColor: "rgba(0, 0, 0, 0.7)",
}
: null,
);
const style = computed(() => {
return theme.value === "dark"
? loading.value
? "background-color: #05040d"
: "background-color: #100c2a"
: "";
});
</script>
<template>
@ -29,8 +46,10 @@ const theme = shallowRef("dark");
<v-chart
:option="option"
autoresize
:loading="loading"
:loading-options="loadingOptions"
:theme="theme"
:style="theme === 'dark' ? 'background-color: #100c2a' : ''"
:style="style"
/>
<template #extra>
<p class="actions">
@ -39,6 +58,8 @@ const theme = shallowRef("dark");
<option :value="null">Default</option>
<option value="dark">Dark</option>
</select>
<input id="loading-check" type="checkbox" v-model="loading" />
<label for="loading-check">Loading</label>
</p>
</template>
</v-example>

View File

@ -4,11 +4,11 @@ import { RadarChart } from "echarts/charts";
import {
PolarComponent,
TitleComponent,
TooltipComponent
TooltipComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import { useScoreStore } from "../data/radar";
use([RadarChart, PolarComponent, TitleComponent, TooltipComponent]);

View File

@ -4,11 +4,11 @@ import { ScatterChart } from "echarts/charts";
import {
GridComponent,
TitleComponent,
LegendComponent
LegendComponent,
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import VChart from "../../src/ECharts";
import VExample from "./Example.vue";
import getData from "../data/scatter";
use([ScatterChart, GridComponent, TitleComponent, LegendComponent]);

18
demo/index.html Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<link
href="https://fonts.googleapis.com/css?family=Inter:300,500;display=swap"
rel="stylesheet"
/>
<title>Vue-ECharts: Vue.js component for Apache ECharts™.</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/main.ts"></script>
</body>
</html>

View File

@ -3,7 +3,17 @@ import { createApp } from "vue";
import { createPinia } from "pinia";
import Demo from "./Demo.vue";
inject();
const SAMPLE_RATE = 0.5;
inject({
beforeSend: (event) => {
if (Math.random() > SAMPLE_RATE) {
return null;
}
return event;
},
});
const pinia = createPinia();
const app = createApp(Demo);

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

6
demo/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
/* eslint-disable */
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

8
demo/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["./**/*", "./**/*.vue"],
"compilerOptions": {
"types": ["vite/client"],
"allowJs": true
}
}

View File

@ -8,6 +8,7 @@ const COMPONENTS_MAP = {
singleAxis: "SingleAxisComponent",
parallel: "ParallelComponent",
calendar: "CalendarComponent",
matrix: "MatrixComponent",
graphic: "GraphicComponent",
toolbox: "ToolboxComponent",
tooltip: "TooltipComponent",
@ -21,6 +22,7 @@ const COMPONENTS_MAP = {
legend: "LegendComponent",
dataZoom: "DataZoomComponent",
visualMap: "VisualMapComponent",
thumbnail: "ThumbnailComponent",
aria: "AriaComponent",
dataset: "DatasetComponent",
@ -28,7 +30,7 @@ const COMPONENTS_MAP = {
xAxis: "GridComponent",
yAxis: "GridComponent",
angleAxis: "PolarComponent",
radiusAxis: "PolarComponent"
radiusAxis: "PolarComponent",
};
const CHARTS_MAP = {
@ -41,6 +43,7 @@ const CHARTS_MAP = {
tree: "TreeChart",
treemap: "TreemapChart",
graph: "GraphChart",
chord: "ChordChart",
gauge: "GaugeChart",
funnel: "FunnelChart",
parallel: "ParallelChart",
@ -53,7 +56,7 @@ const CHARTS_MAP = {
pictorialBar: "PictorialBarChart",
themeRiver: "ThemeRiverChart",
sunburst: "SunburstChart",
custom: "CustomChart"
custom: "CustomChart",
};
const COMPONENTS_GL_MAP = {
@ -66,7 +69,7 @@ const COMPONENTS_GL_MAP = {
// Dependencies
xAxis3D: "Grid3DComponent",
yAxis3D: "Grid3DComponent",
zAxis3D: "Grid3DComponent"
zAxis3D: "Grid3DComponent",
};
const CHARTS_GL_MAP = {
@ -81,17 +84,23 @@ const CHARTS_GL_MAP = {
scatterGL: "ScatterGLChart",
graphGL: "GraphGLChart",
flowGL: "FlowGLChart",
linesGL: "LinesGLChart"
linesGL: "LinesGLChart",
};
const FEATURES = ["UniversalTransition", "LabelLayout"];
const FEATURES = [
"UniversalTransition",
"LabelLayout",
"AxisBreak",
// "LegacyGridContainLabel",
"ScatterJitter",
];
const RENDERERS_MAP = {
canvas: "CanvasRenderer",
svg: "SVGRenderer"
svg: "SVGRenderer",
};
const EXTENSIONS_MAP = {
bmap: "bmap/bmap"
bmap: "bmap/bmap",
// PENDING: There seem no examples that use dataTool
// dataTool: 'dataTool'
};
@ -103,7 +112,7 @@ const INJECTED_COMPONENTS = [
...MARKERS,
"grid",
"axisPointer",
"aria" // TODO aria
"aria", // TODO aria
];
// Component that was dependent.
@ -114,12 +123,12 @@ const DEPENDENT_COMPONENTS = [
"radiusAxis",
"xAxis3D",
"yAxis3D",
"zAxis3D"
"zAxis3D",
];
function createReverseMap(map) {
const reverseMap = {};
Object.keys(map).forEach(key => {
Object.keys(map).forEach((key) => {
// Exclude dependencies.
if (DEPENDENT_COMPONENTS.includes(key)) {
return;
@ -139,7 +148,7 @@ function collectDeps(option) {
let deps = [];
if (option.options) {
// TODO getOption() doesn't have baseOption and options.
option.options.forEach(opt => {
option.options.forEach((opt) => {
deps = deps.concat(collectDeps(opt));
});
@ -151,11 +160,11 @@ function collectDeps(option) {
return Array.from(new Set(deps));
}
Object.keys(option).forEach(key => {
Object.keys(option).forEach((key) => {
if (INJECTED_COMPONENTS.includes(key)) {
return;
}
const val = option[key];
let val = option[key];
if (Array.isArray(val) && !val.length) {
return;
@ -172,12 +181,12 @@ function collectDeps(option) {
}
});
let series = option.series;
if (!Array.isArray(series)) {
series = [series];
}
series.forEach(seriesOpt => {
const series = Array.isArray(option.series) ? option.series : [option.series];
let hasScatterSeries = false;
series.forEach((seriesOpt) => {
if (seriesOpt.type === "scatter") {
hasScatterSeries = true;
}
if (CHARTS_MAP[seriesOpt.type]) {
deps.push(CHARTS_MAP[seriesOpt.type]);
}
@ -191,7 +200,7 @@ function collectDeps(option) {
if (seriesOpt.coordinateSystem === "bmap") {
deps.push("bmap");
}
MARKERS.forEach(markerType => {
MARKERS.forEach((markerType) => {
if (seriesOpt[markerType]) {
deps.push(COMPONENTS_MAP[markerType]);
}
@ -204,9 +213,24 @@ function collectDeps(option) {
deps.push("UniversalTransition");
}
});
Object.keys(option).forEach((key) => {
if (key.endsWith("Axis")) {
const val = option[key];
for (const axisOption of Array.isArray(val) ? val : [val]) {
if (hasScatterSeries && +axisOption.jitter > 0) {
deps.push("ScatterJitter");
}
if (axisOption.breaks && axisOption.breaks.length > 0) {
deps.push("AxisBreak");
}
}
}
});
// Dataset transform
if (option.dataset && Array.isArray(option.dataset)) {
option.dataset.forEach(dataset => {
option.dataset.forEach((dataset) => {
if (dataset.transform) {
deps.push("TransformComponent");
}
@ -225,15 +249,15 @@ function buildMinimalBundleCode(
quote = "'",
multiline = false,
indent = " ",
maxLen = 80
}
maxLen = 80,
},
) {
const options = {
semi,
quote,
multiline,
indent,
maxLen
maxLen,
};
const chartsImports = [];
@ -244,7 +268,7 @@ function buildMinimalBundleCode(
const renderersImports = [];
const extensionImports = [];
deps.forEach(dep => {
deps.forEach((dep) => {
if (dep.endsWith("Renderer")) {
renderersImports.push(dep);
} else if (CHARTS_MAP_REVERSE[dep]) {
@ -278,12 +302,12 @@ function buildMinimalBundleCode(
...componentsGLImports,
...chartsGLImports,
...renderersImports,
...featuresImports
...featuresImports,
];
const ECOptionTypeCode = typeItems(
allImports.filter(a => a.endsWith("Option")),
options
allImports.filter((a) => a.endsWith("Option")),
options,
);
const importSources = [
@ -292,33 +316,33 @@ function buildMinimalBundleCode(
[featuresImports, "echarts/features"],
[renderersImports, "echarts/renderers"],
[chartsGLImports, "echarts-gl/charts"],
[componentsGLImports, "echarts-gl/components"]
].filter(a => a[0].length > 0);
[componentsGLImports, "echarts-gl/components"],
].filter((a) => a[0].length > 0);
const importStatements = importSources.map(([imports, mod]) =>
importItems(
imports.filter(a => !a.endsWith("Option")),
imports.filter((a) => !a.endsWith("Option")),
mod,
options
)
options,
),
);
const semiStr = semi ? ";" : "";
getExtensionDeps(extensionImports, includeType).forEach(ext => {
getExtensionDeps(extensionImports, includeType).forEach((ext) => {
importStatements.push(`import ${quote}${ext}${quote}${semiStr}`);
});
if (includeType) {
importStatements.push(
`import type { ComposeOption } from ${quote}echarts/core${quote}${semiStr}`
`import type { ComposeOption } from ${quote}echarts/core${quote}${semiStr}`,
);
const importTypeStatements = importSources
.map(([imports, mod]) =>
importItems(
imports.filter(a => a.endsWith("Option")),
imports.filter((a) => a.endsWith("Option")),
mod,
{ ...options, type: true }
)
{ ...options, type: true },
),
)
.filter(Boolean);
importStatements.push(...importTypeStatements);
@ -328,16 +352,18 @@ function buildMinimalBundleCode(
${importStatements.join("\n")}
${useItems(
allImports.filter(a => !a.endsWith("Option")),
options
allImports.filter((a) => !a.endsWith("Option")),
options,
)}
${includeType ? `\n${ECOptionTypeCode}` : ""}
`;
}
function getExtensionDeps(deps, ts) {
return deps
.filter(dep => EXTENSIONS_MAP[dep])
.map(dep => `echarts/extension${ts ? "-src" : ""}/${EXTENSIONS_MAP[dep]}`);
.filter((dep) => EXTENSIONS_MAP[dep])
.map(
(dep) => `echarts/extension${ts ? "-src" : ""}/${EXTENSIONS_MAP[dep]}`,
);
}
/** import */
@ -366,7 +392,7 @@ function importSingleLine(items, module, { type, semi, quote }) {
const semiStr = semi ? ";" : "";
return `import ${typeStr}{ ${items.join(
", "
", ",
)} } from ${quote}${module}${quote}${semiStr}`;
}
@ -379,7 +405,7 @@ function importMultiLine(items, module, { type, indent, semi, quote }) {
const semiStr = semi ? ";" : "";
return `import ${typeStr}{
${items.map(item => `${indent}${item}`).join(",\n")}
${items.map((item) => `${indent}${item}`).join(",\n")}
} from ${quote}${module}${quote}${semiStr}`;
}
@ -418,7 +444,7 @@ function useMultiLine(items, { indent, semi }) {
const semiStr = semi ? ";" : "";
return `use([
${items.map(item => `${indent}${item}`).join(`,\n`)}
${items.map((item) => `${indent}${item}`).join(`,\n`)}
])${semiStr}`;
}
@ -457,16 +483,16 @@ function typeMultiLine(items, { indent, semi }) {
const semiStr = semi ? ";" : "";
return `type EChartsOption = ComposeOption<
${items.map(item => `${indent}| ${item}`).join("\n")}
${items.map((item) => `${indent}| ${item}`).join("\n")}
>${semiStr}`;
}
export function getImportsFromOption(
option,
{ renderer = "canvas", ...options }
{ renderer = "canvas", ...options },
) {
return buildMinimalBundleCode(
[...collectDeps(option), RENDERERS_MAP[renderer]],
options
options,
);
}

30
eslint.config.ts Normal file
View File

@ -0,0 +1,30 @@
import {
defineConfigWithVueTs,
vueTsConfigs,
configureVueProject,
} from "@vue/eslint-config-typescript";
import pluginVue from "eslint-plugin-vue";
import skipFormatting from "@vue/eslint-config-prettier/skip-formatting";
// The inferred type of 'default' cannot be named without a reference to "@typescript-eslint/utils"
import type {} from "@typescript-eslint/utils";
// To allow more languages other than `ts` in `.vue` files
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
configureVueProject({ scriptLangs: ["ts", "js"] });
export default defineConfigWithVueTs(
{
name: "app/files-to-lint",
files: ["**/*.{ts,mts,tsx,vue}"],
},
{ ignores: ["**/dist/**"] },
pluginVue.configs["flat/essential"],
vueTsConfigs.recommended,
skipFormatting,
{
rules: {
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},
);

View File

@ -1,88 +1,80 @@
{
"name": "vue-echarts",
"version": "6.6.2",
"description": "Vue.js component for Apache ECharts.",
"version": "8.0.0-beta.1",
"description": "Vue.js component for Apache ECharts.",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/ecomfe/vue-echarts.git"
},
"author": "GU Yiling <justice360@gmail.com>",
"scripts": {
"serve": "vue-cli-service serve",
"build": "pnpm run docs && rimraf dist && pnpm run build:2 && pnpm run build:3 && vue-demi-switch 3",
"build:2": "vue-demi-switch 2 vue2 && rollup -c rollup.vue2.config.js",
"build:3": "vue-demi-switch 3 && rollup -c rollup.config.js",
"lint": "vue-cli-service lint",
"build:demo": "vue-cli-service build",
"docs": "node ./scripts/docs.js",
"postinstall": "node ./scripts/postinstall.js",
"prepublishOnly": "pnpm run build"
"dev": "vite",
"build": "tsdown",
"typecheck": "tsc",
"lint": "eslint . --fix",
"format": "prettier . --write",
"publint": "publint",
"dev:build": "vite build",
"dev:preview": "vite preview",
"dev:typecheck": "vue-tsc -p ./demo",
"docs": "jiti ./scripts/docs.ts",
"release": "pnpm run docs && bumpp --all"
},
"packageManager": "pnpm@10.14.0",
"type": "module",
"main": "dist/index.js",
"unpkg": "dist/index.min.js",
"jsdelivr": "dist/index.min.js",
"types": "dist/index.d.ts",
"exports": {
".": "./dist/index.js",
"./style.css": "./dist/style.css"
},
"main": "dist/index.cjs.min.js",
"module": "dist/index.esm.min.js",
"unpkg": "dist/index.umd.min.js",
"files": [
"dist",
"scripts/postinstall.js"
"dist"
],
"dependencies": {
"resize-detector": "^0.3.0",
"vue-demi": "^0.13.11"
"peerDependencies": {
"echarts": "^6.0.0",
"vue": "^3.3.0"
},
"devDependencies": {
"@babel/core": "^7.23.2",
"@highlightjs/vue-plugin": "^2.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vercel/analytics": "^1.1.1",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/compiler-sfc": "^3.3.7",
"@vue/composition-api": "^1.7.2",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vueuse/core": "^10.5.0",
"comment-mark": "^1.1.1",
"core-js": "^3.33.2",
"echarts": "^5.4.3",
"@types/node": "^22.17.0",
"@typescript-eslint/utils": "^8.39.0",
"@vercel/analytics": "^1.5.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.7.0",
"@vueuse/core": "^13.6.0",
"bumpp": "^10.2.2",
"comment-mark": "^2.0.1",
"echarts": "^6.0.0",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0",
"esbuild-wasm": "^0.19.2",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^8.7.1",
"highlight.js": "^11.9.0",
"pinia": "^2.1.7",
"postcss": "^8.4.31",
"postcss-loader": "^5.3.0",
"postcss-nested": "^5.0.6",
"prettier": "^2.8.8",
"raw-loader": "^4.0.2",
"resize-detector": "^0.3.0",
"rimraf": "^3.0.2",
"rollup": "^2.79.1",
"rollup-plugin-dts": "^4.2.3",
"rollup-plugin-styles": "^4.0.0",
"rollup-plugin-ts": "^2.0.7",
"tslib": "^2.6.2",
"typescript": "4.6.4",
"vue": "^3.3.7",
"vue2": "npm:vue@^2.7.15",
"webpack": "^5.89.0"
"esbuild-wasm": "^0.25.8",
"eslint": "^9.32.0",
"eslint-plugin-vue": "^10.4.0",
"highlight.js": "^11.11.1",
"jiti": "^2.5.1",
"pinia": "^3.0.3",
"postcss-nested": "^7.0.2",
"prettier": "^3.6.2",
"publint": "^0.3.12",
"releaselog": "^6.0.3",
"tsdown": "^0.13.3",
"typescript": "^5.9.2",
"unplugin-raw": "^0.5.1",
"vite": "npm:rolldown-vite@^7.1.0",
"vue": "^3.5.18",
"vue-tsc": "^3.0.5"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.5",
"echarts": "^5.4.1",
"vue": "^2.6.12 || ^3.1.1"
},
"jsdelivr": "dist/index.umd.min.js",
"license": "MIT",
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"echarts": "6"
}
}
},
"repository": "https://github.com/ecomfe/vue-echarts.git",
"types": "dist/index.d.ts"
}
}

10369
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Inter:300,500;display=swap" rel="stylesheet">
<title>Vue-ECharts: Vue.js component for Apache ECharts.</title>
</head>
<body>
<noscript>
<strong>We're sorry but Vue-ECharts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,141 +0,0 @@
import typescript from "rollup-plugin-ts";
import terser from "@rollup/plugin-terser";
import resolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace";
import styles from "rollup-plugin-styles";
import { injectVueDemi } from "./scripts/rollup";
/**
* Modifies the Rollup options for a build to support strict CSP
* @param {import('rollup').RollupOptions} options the original options
* @param {boolean} csp whether to support strict CSP
* @returns {import('rollup').RollupOptions} the modified options
*/
function configBuild(options, csp) {
const result = { ...options };
const { plugins, output } = result;
result.plugins = [
...(csp ? [replace({ __CSP__: `${csp}`, preventAssignment: true })] : []),
...plugins,
csp ? styles({ mode: ["extract", "style.css"] }) : styles()
];
// modify output file names
if (csp && output) {
result.output = (Array.isArray(output) ? output : [output]).map(output => {
return {
...output,
file: output.file.replace(/^dist\//, "dist/csp/"),
assetFileNames: "[name][extname]"
};
});
}
return result;
}
/** @type {import('rollup').RollupOptions[]} */
const builds = [
{
input: "src/index.ts",
plugins: [
typescript({
tsconfig: resolvedConfig => ({ ...resolvedConfig, declaration: true }),
hook: {
outputPath: (path, kind) =>
kind === "declaration" ? "dist/index.d.ts" : path
}
})
],
external: ["vue-demi", "echarts/core", "resize-detector"],
output: {
file: "dist/index.esm.js",
format: "esm",
sourcemap: true
}
},
{
input: "src/index.ts",
plugins: [typescript()],
external: ["vue-demi", "echarts/core", "resize-detector"],
output: [
{
file: "dist/index.esm.min.js",
format: "esm",
sourcemap: true,
plugins: [
terser({
format: {
comments: false
}
})
]
},
{
file: "dist/index.cjs.js",
format: "cjs",
exports: "named",
sourcemap: true
},
{
file: "dist/index.cjs.min.js",
format: "cjs",
exports: "named",
sourcemap: true,
plugins: [
terser({
format: {
comments: false
}
})
]
}
]
},
{
input: "src/global.ts",
plugins: [resolve(), typescript()],
external: ["vue-demi", "echarts", "echarts/core"],
output: [
{
file: "dist/index.umd.js",
format: "umd",
name: "VueECharts",
exports: "default",
sourcemap: true,
globals: {
"vue-demi": "VueDemi",
echarts: "echarts",
"echarts/core": "echarts"
},
plugins: [injectVueDemi]
},
{
file: "dist/index.umd.min.js",
format: "umd",
name: "VueECharts",
exports: "default",
sourcemap: true,
globals: {
"vue-demi": "VueDemi",
echarts: "echarts",
"echarts/core": "echarts"
},
plugins: [
injectVueDemi,
terser({
format: {
comments: false
}
})
]
}
]
}
];
export default [
...builds.map(options => configBuild(options, false)),
...builds.map(options => configBuild(options, true))
];

View File

@ -1,15 +0,0 @@
import dts from "rollup-plugin-dts";
/** @type {import('rollup').RollupOptions[]} */
const options = [
{
input: "src/index.vue2.d.ts",
plugins: [dts()],
output: {
file: "dist/index.vue2.d.ts",
format: "esm"
}
}
];
export default options;

16
scripts/dist-tag.ts Normal file
View File

@ -0,0 +1,16 @@
/** Resolve npm dist-tag from a semver string.
* Usage: jiti scripts/dist-tag.ts 1.2.3-beta.0 # → beta
* jiti scripts/dist-tag.ts # picks version from package.json
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
// Prefer CLI arg, otherwise read package.json
const version: string =
process.argv[2] ??
JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf8")).version;
// Capture first recognised prerelease label
const tag = version.match(/-(alpha|beta|rc)\b/i)?.[1].toLowerCase() ?? "latest";
console.log(tag);

View File

@ -1,60 +0,0 @@
const { readFileSync, writeFileSync } = require("fs");
const { resolve } = require("path");
const commentMark = require("comment-mark");
const { name, version } = require("../package.json");
function resolvePath(...parts) {
return resolve(__dirname, ...parts);
}
const CDN_PREFIX = "https://cdn.jsdelivr.net/npm/";
const DEP_VERSIONS = {
"vue@3": "3.3.7",
"vue@2": "2.7.15",
echarts: "5.4.3",
[name]: version
};
const markConfig = {
vue3Scripts: ["vue@3", "echarts", name],
vue2Scripts: ["vue@2", "echarts", name]
};
function getScripts(version) {
const deps = markConfig[`vue${version}Scripts`];
return deps
.map(dep => {
const [, name] = dep.match(/^(.+?)(?:@.+)?$/) || [];
return `<script src="${CDN_PREFIX}${name}@${DEP_VERSIONS[dep]}"></script>`;
})
.join("\n");
}
function getCodeBlock(code) {
return "```html\n" + code + "\n```";
}
const scripts = {
2: getScripts(2),
3: getScripts(3)
};
const README_FILES = ["README.md", "README.zh-Hans.md"].map(name =>
resolvePath("..", name)
);
README_FILES.forEach(file => {
const content = readFileSync(file, "utf8");
writeFileSync(
file,
commentMark(content, {
vue2Scripts: getCodeBlock(scripts[2]),
vue3Scripts: getCodeBlock(scripts[3])
}),
"utf8"
);
});
console.log("README files updated.");

51
scripts/docs.ts Normal file
View File

@ -0,0 +1,51 @@
import { readFileSync, writeFileSync } from "node:fs";
import { commentMark } from "comment-mark";
import { getPackageVersions, resolvePath } from "./utils";
const { name, version, devDependencies } = getPackageVersions([
"echarts",
"vue",
]);
const CDN_PREFIX = "https://cdn.jsdelivr.net/npm/";
const DEP_VERSIONS = {
...Object.fromEntries(
Object.entries(devDependencies).map(([name, { version }]) => [
name,
version,
]),
),
[name]: version,
};
function getScripts() {
return Object.entries(DEP_VERSIONS)
.map(([dep, version]) => {
const [, name] = dep.match(/^(.+?)(?:@.+)?$/) || [];
return `<script src="${CDN_PREFIX}${name}@${version}"></script>`;
})
.join("\n");
}
function getCodeBlock(code: string) {
return "\n```html\n" + code + "\n```\n";
}
const README_FILES = ["README.md", "README.zh-Hans.md"].map((name) =>
resolvePath(import.meta.url, "..", name),
);
README_FILES.forEach((file) => {
const content = readFileSync(file, "utf8");
writeFileSync(
file,
commentMark(content, {
vue3Scripts: getCodeBlock(getScripts()),
}),
"utf8",
);
});
console.log("README files updated.");

View File

@ -1,43 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const fs = require("fs");
const packageFile = path.resolve(__dirname, "../package.json");
const typesPaths = {
3: "dist/index.d.ts",
2: "dist/index.vue2.d.ts"
};
function switchVersion(version) {
const typesPath = typesPaths[version];
const package = JSON.parse(fs.readFileSync(packageFile, "utf8"));
if (typesPath !== package.types) {
package.types = typesPath;
fs.writeFileSync(packageFile, JSON.stringify(package, null, " "), "utf8");
}
console.log(`[vue-echarts] Switched to Vue ${version} environment.`);
}
function loadVue() {
try {
return require("vue");
} catch (e) {
return null;
}
}
const Vue = loadVue();
// Align the process with vue-demi
if (!Vue || typeof Vue.version !== "string") {
console.warn(
'[vue-echarts] Vue is not found. Please run "npm install vue" to install.'
);
} else if (Vue.version.startsWith("3.")) {
switchVersion(3);
} else if (Vue.version.startsWith("2.")) {
switchVersion(2);
} else {
console.warn(`[vue-echarts] Vue version v${Vue.version} is not supported.`);
}

View File

@ -1,30 +0,0 @@
import { readFileSync } from "fs";
const VUE_DEMI_IIFE = readFileSync(
require.resolve("vue-demi/lib/index.iife.js"),
"utf8"
);
/** @type {import('rollup').Plugin} */
export const injectVueDemi = {
name: "inject-vue-demi",
banner() {
return `${VUE_DEMI_IIFE};\n;`;
}
};
const EMPTY_FILE_ID = "__rollup_empty__";
/** @type {import('rollup').Plugin} */
export const ingoreCss = {
name: "ignore-css",
resolveId(source) {
if (source.endsWith(".css")) {
return EMPTY_FILE_ID;
}
return null;
},
load(id) {
return id === EMPTY_FILE_ID ? "" : null;
}
};

20
scripts/utils.ts Normal file
View File

@ -0,0 +1,20 @@
import { execSync } from "node:child_process";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
export function resolvePath(url: string, ...parts: string[]) {
return resolve(dirname(fileURLToPath(url)), ...parts);
}
type PackageVersions = {
name: string;
version: string;
devDependencies: Record<string, { version: string }>;
};
export function getPackageVersions(devDeps?: string[]): PackageVersions {
const stdOut = execSync(`pnpm ls ${devDeps?.join(" ") || ""} --json`, {
encoding: "utf-8",
});
return JSON.parse(stdOut)[0];
}

6
shims-vue.d.ts vendored
View File

@ -1,6 +0,0 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -1,5 +1,3 @@
/* eslint-disable vue/multi-word-component-names */
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
defineComponent,
shallowRef,
@ -12,15 +10,26 @@ import {
h,
nextTick,
watchEffect,
getCurrentInstance,
Vue2,
type PropType,
type InjectionKey
} from "vue-demi";
toValue,
} from "vue";
import { init as initChart } from "echarts/core";
import {
usePublicAPI,
useAutoresize,
autoresizeProps,
useLoading,
loadingProps,
useSlotOption,
} from "./composables";
import type { PublicMethods, SlotsTypes } from "./composables";
import { isOn, omitOn } from "./utils";
import { register, TAG_NAME } from "./wc";
import type { PropType, InjectionKey } from "vue";
import type {
EChartsType,
EventTarget,
SetOptionType,
Option,
Theme,
ThemeInjection,
@ -28,31 +37,18 @@ import type {
InitOptionsInjection,
UpdateOptions,
UpdateOptionsInjection,
Emits
Emits,
} from "./types";
import {
usePublicAPI,
useAutoresize,
autoresizeProps,
useLoading,
loadingProps
} from "./composables";
import { omitOn, unwrapInjected } from "./utils";
import { register, TAG_NAME, type EChartsElement } from "./wc";
import "./style.css";
import type { EChartsElement } from "./wc";
const __CSP__ = false;
const wcRegistered = __CSP__ ? false : register();
import "./style.ts";
if (Vue2) {
Vue2.config.ignoredElements.push(TAG_NAME);
}
const wcRegistered = register();
export const THEME_KEY = "ecTheme" as unknown as InjectionKey<ThemeInjection>;
export const INIT_OPTIONS_KEY =
"ecInitOptions" as unknown as InjectionKey<InitOptionsInjection>;
export const UPDATE_OPTIONS_KEY =
"ecUpdateOptions" as unknown as InjectionKey<UpdateOptionsInjection>;
export const THEME_KEY: InjectionKey<ThemeInjection> = Symbol();
export const INIT_OPTIONS_KEY: InjectionKey<InitOptionsInjection> = Symbol();
export const UPDATE_OPTIONS_KEY: InjectionKey<UpdateOptionsInjection> =
Symbol();
export { LOADING_OPTIONS_KEY } from "./composables";
export default defineComponent({
@ -60,20 +56,20 @@ export default defineComponent({
props: {
option: Object as PropType<Option>,
theme: {
type: [Object, String] as PropType<Theme>
type: [Object, String] as PropType<Theme>,
},
initOptions: Object as PropType<InitOptions>,
updateOptions: Object as PropType<UpdateOptions>,
group: String,
manualUpdate: Boolean,
...autoresizeProps,
...loadingProps
...loadingProps,
},
emits: {} as unknown as Emits,
slots: Object as SlotsTypes,
inheritAttrs: false,
setup(props, { attrs }) {
setup(props, { attrs, expose, slots }) {
const root = shallowRef<EChartsElement>();
const inner = shallowRef<HTMLElement>();
const chart = shallowRef<EChartsType>();
const manualOption = shallowRef<Option>();
const defaultTheme = inject(THEME_KEY, null);
@ -83,80 +79,90 @@ export default defineComponent({
const { autoresize, manualUpdate, loading, loadingOptions } = toRefs(props);
const realOption = computed(
() => manualOption.value || props.option || null
() => manualOption.value || props.option || null,
);
const realTheme = computed(
() => props.theme || unwrapInjected(defaultTheme, {})
() => props.theme || toValue(defaultTheme) || {},
);
const realInitOptions = computed(
() => props.initOptions || unwrapInjected(defaultInitOptions, {})
() => props.initOptions || toValue(defaultInitOptions) || {},
);
const realUpdateOptions = computed(
() => props.updateOptions || unwrapInjected(defaultUpdateOptions, {})
() => props.updateOptions || toValue(defaultUpdateOptions) || {},
);
const nonEventAttrs = computed(() => omitOn(attrs));
const nativeListeners: Record<string, unknown> = {};
// @ts-expect-error listeners for Vue 2 compatibility
const listeners = getCurrentInstance().proxy.$listeners;
const listeners: Map<{ event: string; once?: boolean; zr?: boolean }, any> =
new Map();
const { teleportedSlots, patchOption } = useSlotOption(slots, () => {
if (!manualUpdate.value && props.option && chart.value) {
chart.value.setOption(
patchOption(props.option),
realUpdateOptions.value,
);
}
});
// We are converting all `on<Event>` props and collect them into `listeners` so that
// we can bind them to the chart instance later.
// For `onNative:<event>` props, we just strip the `Native:` part and collect them into
// `nativeListeners` so that we can bind them to the root element directly.
Object.keys(attrs)
.filter((key) => isOn(key))
.forEach((key) => {
// Collect native DOM events
if (key.indexOf("Native:") === 2) {
// onNative:click -> onClick
const nativeKey = `on${key.charAt(9).toUpperCase()}${key.slice(10)}`;
nativeListeners[nativeKey] = attrs[key];
return;
}
// onClick -> c + lick
// onZr:click -> z + r:click
let event = key.charAt(2).toLowerCase() + key.slice(3);
let zr: boolean | undefined;
if (event.indexOf("zr:") === 0) {
zr = true;
event = event.substring(3);
}
let once: boolean | undefined;
if (event.substring(event.length - 4) === "Once") {
once = true;
event = event.substring(0, event.length - 4);
}
listeners.set({ event, zr, once }, attrs[key]);
});
function init(option?: Option) {
if (!inner.value) {
if (!root.value) {
return;
}
const instance = (chart.value = initChart(
inner.value,
root.value,
realTheme.value,
realInitOptions.value
realInitOptions.value,
));
if (props.group) {
instance.group = props.group;
}
let realListeners = listeners;
if (!realListeners) {
realListeners = {};
Object.keys(attrs)
.filter(key => key.indexOf("on") === 0 && key.length > 2)
.forEach(key => {
// onClick -> c + lick
// onZr:click -> z + r:click
let event = key.charAt(2).toLowerCase() + key.slice(3);
// clickOnce -> ~click
// zr:clickOnce -> ~zr:click
if (event.substring(event.length - 4) === "Once") {
event = `~${event.substring(0, event.length - 4)}`;
}
realListeners[event] = attrs[key];
});
}
Object.keys(realListeners).forEach(key => {
let handler = realListeners[key];
listeners.forEach((handler, { zr, once, event }) => {
if (!handler) {
return;
}
let event = key.toLowerCase();
if (event.charAt(0) === "~") {
event = event.substring(1);
handler.__once__ = true;
}
let target: EventTarget = instance;
if (event.indexOf("zr:") === 0) {
target = instance.getZr();
event = event.substring(3);
}
if (handler.__once__) {
delete handler.__once__;
const target = zr ? instance.getZr() : instance;
if (once) {
const raw = handler;
handler = (...args: any[]) => {
@ -180,7 +186,7 @@ export default defineComponent({
function commit() {
const opt = option || realOption.value;
if (opt) {
instance.setOption(opt, realUpdateOptions.value);
instance.setOption(patchOption(opt), realUpdateOptions.value);
}
}
@ -195,8 +201,14 @@ export default defineComponent({
commit();
}
}
const setOption: SetOptionType = (
option,
notMerge,
lazyUpdate?: boolean,
) => {
const updateOptions =
typeof notMerge === "boolean" ? { notMerge, lazyUpdate } : notMerge;
function setOption(option: Option, updateOptions?: UpdateOptions) {
if (props.manualUpdate) {
manualOption.value = option;
}
@ -204,9 +216,9 @@ export default defineComponent({
if (!chart.value) {
init(option);
} else {
chart.value.setOption(option, updateOptions || {});
chart.value.setOption(patchOption(option), updateOptions);
}
}
};
function cleanup() {
if (chart.value) {
@ -218,7 +230,7 @@ export default defineComponent({
let unwatchOption: (() => void) | null = null;
watch(
manualUpdate,
manualUpdate => {
(manualUpdate) => {
if (typeof unwatchOption === "function") {
unwatchOption();
unwatchOption = null;
@ -234,32 +246,42 @@ export default defineComponent({
if (!chart.value) {
init();
} else {
chart.value.setOption(option, {
chart.value.setOption(patchOption(option), {
// mutating `option` will lead to `notMerge: false` and
// replacing it with new reference will lead to `notMerge: true`
notMerge: option !== oldOption,
...realUpdateOptions.value
...realUpdateOptions.value,
});
}
},
{ deep: true }
{ deep: true },
);
}
},
{
immediate: true
}
immediate: true,
},
);
watch(
[realTheme, realInitOptions],
realInitOptions,
() => {
cleanup();
init();
},
{
deep: true
}
deep: true,
},
);
watch(
realTheme,
(theme) => {
chart.value?.setTheme(theme);
},
{
deep: true,
},
);
watchEffect(() => {
@ -272,7 +294,7 @@ export default defineComponent({
useLoading(chart, loading, loadingOptions);
useAutoresize(chart, autoresize, inner);
useAutoresize(chart, autoresize, root);
onMounted(() => {
init();
@ -290,23 +312,27 @@ export default defineComponent({
}
});
return {
chart,
root,
inner,
const exposed = {
setOption,
nonEventAttrs,
...publicApi
root,
chart,
};
expose({ ...exposed, ...publicApi });
// While `expose()` exposes methods and properties to the parent component
// via template refs at runtime, it doesn't contribute to TypeScript types.
// This type casting ensures TypeScript correctly types the exposed members
// that will be available when using this component.
return (() =>
h(
TAG_NAME,
{
...nonEventAttrs.value,
...nativeListeners,
ref: root,
class: ["echarts", nonEventAttrs.value.class],
},
teleportedSlots(),
)) as unknown as typeof exposed & PublicMethods;
},
render() {
// Vue 3 and Vue 2 have different vnode props format:
// See https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#vnode-props-format
const attrs = (
Vue2 ? { attrs: this.nonEventAttrs } : { ...this.nonEventAttrs }
) as any;
attrs.ref = "root";
attrs.class = attrs.class ? ["echarts"].concat(attrs.class) : "echarts";
return h(TAG_NAME, attrs, [h("div", { ref: "inner" })]);
}
});

View File

@ -1,6 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Ref } from "vue-demi";
import { EChartsType } from "../types";
import type { Ref } from "vue";
import type { EChartsType } from "../types";
const METHOD_NAMES = [
"getWidth",
@ -17,18 +16,18 @@ const METHOD_NAMES = [
"appendData",
"clear",
"isDisposed",
"dispose"
"dispose",
] as const;
type MethodName = (typeof METHOD_NAMES)[number];
type PublicMethods = Pick<EChartsType, MethodName>;
export type PublicMethods = Pick<EChartsType, MethodName>;
export function usePublicAPI(
chart: Ref<EChartsType | undefined>
chart: Ref<EChartsType | undefined>,
): PublicMethods {
function makePublicMethod<T extends MethodName>(
name: T
name: T,
): (...args: Parameters<EChartsType[T]>) => ReturnType<EChartsType[T]> {
return (...args) => {
if (!chart.value) {
@ -40,7 +39,7 @@ export function usePublicAPI(
function makePublicMethods(): PublicMethods {
const methods = Object.create(null);
METHOD_NAMES.forEach(name => {
METHOD_NAMES.forEach((name) => {
methods[name] = makePublicMethod(name);
});

View File

@ -1,48 +1,66 @@
import { watch, type Ref, type PropType } from "vue-demi";
import { watch } from "vue";
import { throttle } from "echarts/core";
import {
addListener,
removeListener,
type ResizeCallback
} from "resize-detector";
import { type EChartsType } from "../types";
type AutoresizeProp =
| boolean
| {
throttle?: number;
onResize?: () => void;
};
import type { Ref, PropType } from "vue";
import type { EChartsType, AutoResize } from "../types";
export function useAutoresize(
chart: Ref<EChartsType | undefined>,
autoresize: Ref<AutoresizeProp | undefined>,
root: Ref<HTMLElement | undefined>
autoresize: Ref<AutoResize | undefined>,
root: Ref<HTMLElement | undefined>,
): void {
let resizeListener: ResizeCallback | null = null;
watch(
[root, chart, autoresize],
([root, chart, autoresize], _, onCleanup) => {
let ro: ResizeObserver | null = null;
watch([root, chart, autoresize], ([root, chart, autoresize], _, cleanup) => {
if (root && chart && autoresize) {
const autoresizeOptions = autoresize === true ? {} : autoresize;
const { throttle: wait = 100, onResize } = autoresizeOptions;
if (root && chart && autoresize) {
const { offsetWidth, offsetHeight } = root;
const autoresizeOptions = autoresize === true ? {} : autoresize;
const { throttle: wait = 100, onResize } = autoresizeOptions;
const callback = () => {
chart.resize();
onResize?.();
};
let initialResizeTriggered = false;
resizeListener = wait ? throttle(callback, wait) : callback;
addListener(root, resizeListener);
}
const callback = () => {
chart.resize();
onResize?.();
};
cleanup(() => {
if (root && resizeListener) {
removeListener(root, resizeListener);
const resizeCallback = wait ? throttle(callback, wait) : callback;
ro = new ResizeObserver(() => {
// We just skip ResizeObserver's initial resize callback if the
// size has not changed since the chart is rendered.
if (!initialResizeTriggered) {
initialResizeTriggered = true;
if (
root.offsetWidth === offsetWidth &&
root.offsetHeight === offsetHeight
) {
return;
}
}
// Skip if container has zero size
if (root.offsetWidth === 0 || root.offsetHeight === 0) {
return;
}
resizeCallback();
});
ro.observe(root);
}
});
});
onCleanup(() => {
if (ro) {
ro.disconnect();
ro = null;
}
});
},
);
}
export const autoresizeProps = {
autoresize: [Boolean, Object] as PropType<AutoresizeProp>
autoresize: [Boolean, Object] as PropType<AutoResize>,
};

View File

@ -1,3 +1,4 @@
export * from "./api";
export * from "./autoresize";
export * from "./loading";
export * from "./slot";

View File

@ -1,28 +1,24 @@
import { unwrapInjected } from "../utils";
import {
inject,
computed,
watchEffect,
type Ref,
type InjectionKey,
type PropType
} from "vue-demi";
import type { EChartsType, LoadingOptions } from "../types";
import { inject, computed, watchEffect, toValue } from "vue";
export const LOADING_OPTIONS_KEY =
"ecLoadingOptions" as unknown as InjectionKey<
LoadingOptions | Ref<LoadingOptions>
>;
import type { Ref, InjectionKey, PropType } from "vue";
import type {
EChartsType,
LoadingOptions,
LoadingOptionsInjection,
} from "../types";
export const LOADING_OPTIONS_KEY: InjectionKey<LoadingOptionsInjection> =
Symbol();
export function useLoading(
chart: Ref<EChartsType | undefined>,
loading: Ref<boolean>,
loadingOptions: Ref<LoadingOptions | undefined>
loading: Ref<boolean | undefined>,
loadingOptions: Ref<LoadingOptions | undefined>,
): void {
const defaultLoadingOptions = inject(LOADING_OPTIONS_KEY, {});
const realLoadingOptions = computed(() => ({
...unwrapInjected(defaultLoadingOptions, {}),
...loadingOptions?.value
...toValue(defaultLoadingOptions),
...loadingOptions?.value,
}));
watchEffect(() => {
@ -41,5 +37,5 @@ export function useLoading(
export const loadingProps = {
loading: Boolean,
loadingOptions: Object as PropType<LoadingOptions>
loadingOptions: Object as PropType<LoadingOptions>,
};

146
src/composables/slot.ts Normal file
View File

@ -0,0 +1,146 @@
import {
h,
Teleport,
onUpdated,
onUnmounted,
onMounted,
shallowRef,
shallowReactive,
warn,
} from "vue";
import type { Slots, SlotsType } from "vue";
import type { Option } from "../types";
import { isValidArrayIndex, isSameSet } from "../utils";
import type { TooltipComponentFormatterCallbackParams } from "echarts";
const SLOT_OPTION_PATHS = {
tooltip: ["tooltip", "formatter"],
dataView: ["toolbox", "feature", "dataView", "optionToContent"],
} as const;
type SlotPrefix = keyof typeof SLOT_OPTION_PATHS;
type SlotName = SlotPrefix | `${SlotPrefix}-${string}`;
type SlotRecord<T> = Partial<Record<SlotName, T>>;
const SLOT_PREFIXES = Object.keys(SLOT_OPTION_PATHS) as SlotPrefix[];
function isValidSlotName(key: string): key is SlotName {
return SLOT_PREFIXES.some(
(slotPrefix) => key === slotPrefix || key.startsWith(slotPrefix + "-"),
);
}
export function useSlotOption(slots: Slots, onSlotsChange: () => void) {
const detachedRoot =
typeof window !== "undefined" ? document.createElement("div") : undefined;
const containers = shallowReactive<SlotRecord<HTMLElement>>({});
const initialized = shallowReactive<SlotRecord<boolean>>({});
const params = shallowReactive<SlotRecord<unknown>>({});
const isMounted = shallowRef(false);
// Teleport the slots to a detached root
const teleportedSlots = () => {
// Make slots client-side only to avoid SSR hydration mismatch
return isMounted.value
? h(
Teleport,
{ to: detachedRoot },
Object.entries(slots)
.filter(([key]) => isValidSlotName(key))
.map(([key, slot]) => {
const slotName = key as SlotName;
const slotContent = initialized[slotName]
? slot?.(params[slotName])
: undefined;
return h(
"div",
{
ref: (el) => (containers[slotName] = el as HTMLElement),
style: { display: "contents" },
},
slotContent,
);
}),
)
: undefined;
};
// Shallow-clone the option along the path and override the target callback
function patchOption(src: Option): Option {
const root = { ...src };
Object.keys(slots)
.filter((key) => {
const isValidSlot = isValidSlotName(key);
if (!isValidSlot) {
warn(`Invalid vue-echarts slot name: ${key}`);
}
return isValidSlot;
})
.forEach((key) => {
const path = key.split("-");
const prefix = path.shift() as SlotPrefix;
path.push(...SLOT_OPTION_PATHS[prefix]);
let cur: any = root;
for (let i = 0; i < path.length - 1; i++) {
const seg = path[i];
const next = cur[seg];
// Shallow-clone the link; create empty shell if missing
cur[seg] = next
? Array.isArray(next)
? [...next]
: { ...next }
: isValidArrayIndex(seg)
? []
: {};
cur = cur[seg];
}
cur[path[path.length - 1]] = (p: unknown) => {
initialized[key] = true;
params[key] = p;
return containers[key];
};
});
return root;
}
// `slots` is not reactive, so we need to watch it manually
let slotNames: SlotName[] = [];
onUpdated(() => {
const newSlotNames = Object.keys(slots).filter(isValidSlotName);
if (!isSameSet(newSlotNames, slotNames)) {
// Clean up states for removed slots
slotNames.forEach((key) => {
if (!newSlotNames.includes(key)) {
delete params[key];
delete initialized[key];
delete containers[key];
}
});
slotNames = newSlotNames;
onSlotsChange();
}
});
onMounted(() => {
isMounted.value = true;
});
onUnmounted(() => {
detachedRoot?.remove();
});
return {
teleportedSlots,
patchOption,
};
}
export type SlotsTypes = SlotsType<
Record<
"tooltip" | `tooltip-${string}`,
TooltipComponentFormatterCallbackParams
> &
Record<"dataView" | `dataView-${string}`, Option>
>;

View File

@ -1,6 +0,0 @@
{
"files": [
"./Demo.vue",
"./examples/RadarChart.vue"
],
}

View File

@ -3,5 +3,5 @@ import ECharts, * as exported from "./index";
export default {
...ECharts,
...exported
...exported,
};

63
src/index.vue2.d.ts vendored
View File

@ -1,63 +0,0 @@
/* eslint-disable @typescript-eslint/ban-types */
import type { Ref, DefineComponent } from "vue-demi";
import type {
Option,
InitOptions,
UpdateOptions,
EChartsType,
Emits
} from "./types";
declare const LOADING_OPTIONS_KEY = "ecLoadingOptions";
declare const THEME_KEY = "ecTheme";
declare const INIT_OPTIONS_KEY = "ecInitOptions";
declare const UPDATE_OPTIONS_KEY = "ecUpdateOptions";
declare type ChartProps = {
loading?: boolean;
loadingOptions?: Record<string, unknown>;
autoresize?: boolean;
option?: Option;
theme?: string | Record<string, unknown>;
initOptions?: InitOptions;
updateOptions?: UpdateOptions;
group?: string;
manualUpdate?: boolean;
};
type MethodNames =
| "getWidth"
| "getHeight"
| "getDom"
| "getOption"
| "resize"
| "dispatchAction"
| "convertToPixel"
| "convertFromPixel"
| "containPixel"
| "getDataURL"
| "getConnectedDataURL"
| "appendData"
| "clear"
| "isDisposed"
| "dispose"
| "setOption";
declare type ChartMethods = Pick<EChartsType, MethodNames>;
declare const Chart: DefineComponent<
ChartProps,
{
root: Ref<HTMLElement | undefined>;
chart: Ref<EChartsType | undefined>;
},
{},
{},
ChartMethods,
{},
{},
Emits
>;
export default Chart;
export { INIT_OPTIONS_KEY, LOADING_OPTIONS_KEY, THEME_KEY, UPDATE_OPTIONS_KEY };

View File

@ -1 +1 @@
x-vue-echarts{display:block;width:100%;height:100%;min-width:0}x-vue-echarts>[_echarts_instance_]{width:100%;height:100%}
x-vue-echarts{display:block;width:100%;height:100%;min-width:0;}

16
src/style.ts Normal file
View File

@ -0,0 +1,16 @@
import cssRules from "./style.css?raw";
if (typeof document !== "undefined") {
if (
Array.isArray(document.adoptedStyleSheets) &&
"replaceSync" in CSSStyleSheet.prototype
) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssRules);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
} else {
const styleEl = document.createElement("style");
styleEl.textContent = cssRules;
document.head.appendChild(styleEl);
}
}

View File

@ -1,29 +1,30 @@
import { init } from "echarts/core";
import type {
SetOptionOpts,
ECElementEvent,
ElementEvent,
EChartsOption
} from "echarts";
import type { Ref } from "vue";
export type Injection<T> = T | null | Ref<T | null> | { value: T | null };
import type { SetOptionOpts, ECElementEvent, ElementEvent } from "echarts/core";
import type { MaybeRefOrGetter } from "vue";
export type Injection<T> = MaybeRefOrGetter<T | null>;
type InitType = typeof init;
export type InitParameters = Parameters<InitType>;
export type Theme = NonNullable<InitParameters[1]>;
export type ThemeInjection = Injection<Theme>;
export type InitOptions = NonNullable<InitParameters[2]>;
export type InitOptionsInjection = Injection<InitOptions>;
export type UpdateOptions = SetOptionOpts;
export type UpdateOptionsInjection = Injection<UpdateOptions>;
export type EChartsType = ReturnType<InitType>;
type ZRenderType = ReturnType<EChartsType["getZr"]>;
export type EventTarget = EChartsType | ZRenderType;
export type Option = EChartsOption;
export type SetOptionType = EChartsType["setOption"];
export type Option = Parameters<SetOptionType>[0];
export type AutoResize =
| boolean
| {
throttle?: number;
onResize?: () => void;
};
export type LoadingOptions = {
text?: string;
@ -39,6 +40,7 @@ export type LoadingOptions = {
lineWidth?: number;
zlevel?: number;
};
export type LoadingOptionsInjection = Injection<LoadingOptions>;
type MouseEventName =
| "click"
@ -94,19 +96,19 @@ type OtherEventName =
| "globalcursortaken";
type MouseEmits = {
[key in MouseEventName]: (params: ECElementEvent) => boolean;
[key in MouseEventName]: (params: ECElementEvent) => void;
};
type ZRenderEmits = {
[key in ZRenderEventName]: (params: ElementEvent) => boolean;
[key in ZRenderEventName]: (params: ElementEvent) => void;
};
type OtherEmits = {
[key in OtherEventName]: null;
[key in OtherEventName]: (params: any) => void;
};
export type Emits = MouseEmits &
OtherEmits & {
rendered: (params: { elapsedTime: number }) => boolean;
finished: () => boolean;
rendered: (params: { elapsedTime: number }) => void;
finished: () => void;
} & ZRenderEmits;

View File

@ -1,10 +1,4 @@
import { unref } from "vue-demi";
import type { Injection } from "./types";
type Attrs = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
type Attrs = Record<string, any>;
// Copied from
// https://github.com/vuejs/vue-next/blob/5a7a1b8293822219283d6e267496bec02234b0bc/packages/shared/src/index.ts#L40-L41
@ -22,15 +16,25 @@ export function omitOn(attrs: Attrs): Attrs {
return result;
}
export function unwrapInjected<T, V>(
injection: Injection<T>,
defaultValue: V
): T | V {
const value = unref(injection);
export function isValidArrayIndex(key: string): boolean {
const num = Number(key);
return (
Number.isInteger(num) &&
num >= 0 &&
num < Math.pow(2, 32) - 1 &&
String(num) === key
);
}
if (value && typeof value === "object" && "value" in value) {
return value.value || defaultValue;
export function isSameSet<T>(a: T[], b: T[]): boolean {
const setA = new Set(a);
const setB = new Set(b);
if (setA.size !== setB.size) return false;
for (const val of setA) {
if (!setB.has(val)) return false;
}
return value || defaultValue;
return true;
}

View File

@ -19,31 +19,20 @@ export function register(): boolean {
}
try {
// Class definitions cannot be transpiled to ES5
// so we are doing a little trick here to ensure
// we are using native classes. As we use this as
// a progressive enhancement, it will be fine even
// if the browser doesn't support native classes.
const reg = new Function(
"tag",
`class EChartsElement extends HTMLElement {
__dispose = null;
class ECElement extends HTMLElement implements EChartsElement {
__dispose: (() => void) | null = null;
disconnectedCallback() {
if (this.__dispose) {
this.__dispose();
this.__dispose = null;
disconnectedCallback() {
if (this.__dispose) {
this.__dispose();
this.__dispose = null;
}
}
}
}
}
if (customElements.get(tag) == null) {
customElements.define(tag, EChartsElement);
}
`
);
reg(TAG_NAME);
} catch (e) {
if (customElements.get(TAG_NAME) == null) {
customElements.define(TAG_NAME, ECElement);
}
} catch {
return (registered = false);
}

View File

@ -1,27 +1,25 @@
{
"allowJs": true,
"compilerOptions": {
"target": "ES5",
"target": "ESNext",
"module": "ESNext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"removeComments": true,
"skipLibCheck": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"noEmit": true,
"baseUrl": ".",
"types": ["vite/client"],
"lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"shims-vue.d.ts",
"src/demo/**/*.ts",
"src/demo/**/*.vue"
],
"exclude": ["node_modules"]
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

13
tsconfig.node.json Normal file
View File

@ -0,0 +1,13 @@
{
"include": ["*.config.*", "scripts/**/*"],
"compilerOptions": {
"composite": true,
"lib": ["ESNext"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

32
tsdown.config.ts Normal file
View File

@ -0,0 +1,32 @@
import { defineConfig } from "tsdown";
import raw from "unplugin-raw/rollup";
export default defineConfig([
{
entry: "src/index.ts",
platform: "browser",
sourcemap: true,
copy: ["src/style.css"],
plugins: [raw()],
},
{
entry: "src/global.ts",
outputOptions: {
file: "dist/index.min.js", // for unpkg/jsdelivr
dir: undefined,
format: "umd",
name: "VueECharts",
exports: "default",
globals: {
vue: "Vue",
echarts: "echarts",
"echarts/core": "echarts",
},
},
platform: "browser",
sourcemap: true,
minify: true,
dts: false,
plugins: [raw()],
},
]);

5
vercel.json Normal file
View File

@ -0,0 +1,5 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "pnpm run dev:build",
"outputDirectory": "demo/dist"
}

17
vite.config.ts Normal file
View File

@ -0,0 +1,17 @@
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import postcssNested from "postcss-nested";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
root: "./demo",
server: {
host: true,
},
css: {
postcss: {
plugins: [postcssNested()],
},
},
});

View File

@ -1,34 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const nested = require("postcss-nested");
module.exports = {
outputDir: "demo",
css: {
loaderOptions: {
postcss: {
postcssOptions: {
plugins: [nested()]
}
}
}
},
chainWebpack: config => {
config.entry("app").clear().add("./src/demo/main.ts");
config.module
.rule("svg")
.clear()
.test(/\.svg$/)
.type("asset/source");
config.plugin("define").tap(([options]) => [
{
...options,
__CSP__: "false"
}
]);
},
devServer: {
allowedHosts: "all"
}
};