Compare commits

...

104 Commits

Author SHA1 Message Date
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
8132842be7 fix: fixed that tooltips may affected by internal styling by VueECharts 2023-12-04 16:59:34 +08:00
a2f8f0010a chore: update readme [ci skip] 2023-11-01 18:11:27 +08:00
70c32603d4 fix: revert to esbuild-wasm@0.19.2 2023-10-31 19:19:24 +08:00
19cc931595 chore: update deps 2023-10-31 10:36:55 +08:00
21fae71b9e chore: improve demo analytics for copy 2023-09-14 14:55:18 +08:00
ccb951e11b chore: improve demo code 2023-09-13 12:57:34 +08:00
1a3d7e2004 chore: refine readme 2023-09-13 12:13:45 +08:00
c41d55b054 chore: update demo 2023-09-12 20:36:54 +08:00
4484b4e180 fix: improve codegen output 2023-09-04 17:16:28 +08:00
705bff310f fix: fix codegen focus 2023-09-01 12:21:32 +08:00
5985dd8c86 fix: fix codegen style 2023-09-01 12:00:50 +08:00
ef8d368638 chore: track copy more precisely 2023-09-01 10:29:44 +08:00
125cbd3d53 chore: track copy 2023-08-31 22:26:09 +08:00
bd862426ea feat: use esbuild to support ts for codegen 2023-08-31 22:14:55 +08:00
29bf99420d feat: add custom event tracking 2023-08-30 20:41:37 +08:00
f5bebc0920 chore: add codegen screenshot 2023-08-30 20:31:50 +08:00
b843f805a5 chore: update readme 2023-08-30 20:20:55 +08:00
66988fab4f docs: add import codegen 2023-08-30 19:04:15 +08:00
c103128c99 chore: update readme [skip ci] 2023-08-28 14:41:34 +08:00
eb59b4b185 chore: refine demo 2023-08-23 16:04:55 +08:00
03a3a6a401 docs: update README.md - remove --save instruction (#735)
According to https://blog.npmjs.org/post/161081169345/v500.html . The --save options is default since npm version 5
2023-08-17 23:15:25 +08:00
088f18ac80 chore: update readme 2023-08-17 20:10:37 +08:00
3e4615f94b chore: improve demo 2023-08-16 14:31:32 +08:00
e224c49951 fix: improve types for option 2023-08-16 14:31:23 +08:00
f8e7597193 chore: update readme 2023-08-07 10:09:12 +08:00
aa22d332ed refactor: refactor demo 2023-08-05 01:02:49 +08:00
82a098c416 feat: make css paddings work out-of-the-box 2023-08-05 01:02:32 +08:00
3da9f285ff chore: update deps 2023-08-01 19:54:01 +08:00
e48067c5d3 chore: add vercel analytics 2023-08-01 15:19:20 +08:00
53ebc2941e chore: update changelog 2023-06-13 20:45:38 +08:00
e8697382a1 feat: add options for autoresize
* add `throttle: number` to specify throttle delay (#419 #562)
* add `onResize: () => void` to specify a resize callback (#714)
2023-06-13 20:35:45 +08:00
47f7885f19 chore: add pinia + radar demo 2023-06-13 20:24:53 +08:00
9c34d682c4 docs: update readme 2023-04-24 13:17:06 +08:00
87d4811509 chore: fix demo, update version and changelog 2023-04-24 13:14:18 +08:00
d082883bc5 fix: no longer use custom element enhancement for strict csp builds, fix #707 2023-04-24 12:10:19 +08:00
b86280e1f0 fix: cleaned up the console.log call sneaked in by mistake 2023-02-08 16:32:30 +08:00
6159c7d684 fix: fix default behavior for notMerge, close #691 2023-02-08 16:22:51 +08:00
4313d301e4 docs: add codesandbox link 2023-02-03 14:55:08 +08:00
c13b482e12 chore: allow all hosts for devserver 2023-02-03 11:22:11 +08:00
b003b07222 docs: add codeflow link 2023-02-02 15:15:19 +08:00
89d15a9ee3 chore: remove esm deps 2023-02-02 00:45:29 +08:00
81272bfa0f chore: update stackblitz config, only build before publish 2023-02-02 00:40:24 +08:00
f554c89c58 chore: remove esm deps and unused files 2023-02-02 00:04:27 +08:00
5f7f9c0d4c feat: handle csp, extract css 2023-01-28 23:52:11 +08:00
f5819e4361 chore: update deps and version 2023-01-09 12:58:10 +08:00
16b9274ae1 fix: fix types for mouse events 2023-01-09 12:53:58 +08:00
cf963efc6d docs: sync chinese readme 2023-01-08 23:41:15 +08:00
fd36c5120b docs: @nuxtjs/composition-api is still needed for 2.7+ 2023-01-08 23:39:55 +08:00
4fab6c272b chore: make emit type a little more consistent with runtime 2023-01-08 23:38:03 +08:00
0edfb3936c types: use more precise typings for event handlers, update peer deps for echarts 2023-01-02 23:41:40 +08:00
ed4bd392ff chore: update readme version 2023-01-01 23:52:32 +08:00
60 changed files with 10516 additions and 7113 deletions

View File

@ -1,24 +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"
},
overrides: [
{
files: ["*.ts"],
extends: [
"@vue/typescript/recommended",
"@vue/prettier/@typescript-eslint"
]
}
]
};

20
.eslintrc.json Normal file
View File

@ -0,0 +1,20 @@
{
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier/skip-formatting"
],
"parserOptions": {
"ecmaVersion": 2020,
"parser": "@typescript-eslint/parser"
},
"rules": {
"no-console": "off",
"vue/multi-word-component-names": "off"
}
}

4
.stackblitzrc Normal file
View File

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

View File

@ -1,3 +1,114 @@
## 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.
## 6.6.1
* 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.
## 6.5.5
* 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.
## 6.5.3
* Fixed default behavior for `notMerge` option (#691).
## 6.5.2
* Added `dist/csp/*` to support strict CSP with extracted CSS file.
## 6.5.1
* 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`.
## 6.4.1
* Improve typings for mouse event params.

234
README.md
View File

@ -1,39 +1,21 @@
# Vue-ECharts
<h1 align="center">Vue-ECharts</h1>
> Vue.js component for Apache ECharts.
<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>
> [🇨🇳 中文版](./README.zh-Hans.md)
---
Uses [Apache ECharts](https://echarts.apache.org/en/index.html) 5 and works for both [Vue.js](https://vuejs.org/) 2/3.
## 💡 Heads up 💡
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 v6? Read v6 docs [here →](https://github.com/ecomfe/vue-echarts/tree/6.x)
## Installation & Usage
### npm & ESM
```bash
$ npm install echarts vue-echarts
```
To make `vue-echarts` work for _Vue 2_ (<2.7.0), you need to have `@vue/composition-api` installed:
### npm
```sh
npm i -D @vue/composition-api
npm add echarts vue-echarts
```
If you are using _NuxtJS_ on top of _Vue 2_ (<2.7.0), you'll also need `@nuxtjs/composition-api`:
```sh
npm i -D @nuxtjs/composition-api
```
And then add `'@nuxtjs/composition-api/module'` in the `buildModules` option in your `nuxt.config.js`.
#### Example
<details>
@ -208,7 +190,12 @@ export default {
</details>
We encourage manually importing components and charts from ECharts for smaller bundle size. See all supported renderers/charts/components [here →](https://github.com/apache/echarts/blob/master/src/echarts.all.ts)
> [!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:
@ -216,7 +203,7 @@ 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`.
@ -225,9 +212,9 @@ Drop `<script>` inside your HTML file and access the component via `window.VueEC
<!-- vue3Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.33"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@7.0.0"></script>
```
<!-- vue3Scripts:end -->
@ -245,9 +232,9 @@ app.component('v-chart', VueECharts)
<!-- vue2Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.5"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@7.0.0"></script>
```
<!-- vue2Scripts:end -->
@ -290,9 +277,9 @@ See more examples [here](https://github.com/ecomfe/vue-echarts/tree/main/src/dem
Group name to be used in chart [connection](https://echarts.apache.org/en/api.html#echarts.connect). See `echartsInstance.group` [here →](https://echarts.apache.org/en/api.html#echartsInstance.group)
- `autoresize: boolean` (default: `false`)
- `autoresize: boolean | { throttle?: number, onResize?: () => void }` (default: `false`)
Whether the chart should be resized automatically whenever its root is resized.
Whether the chart should be resized automatically whenever its root is resized. Use the options object to specify a custom throttle delay (in milliseconds) and/or an extra resize callback function.
- `loading: boolean` (default: `false`)
@ -308,6 +295,78 @@ 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.
### Events
You can bind events with Vue's `v-on` directive.
```vue
<template>
<v-chart :option="option" @highlight="handleHighlight" />
</template>
```
> **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:
- `highlight` [](https://echarts.apache.org/en/api.html#events.highlight)
- `downplay` [](https://echarts.apache.org/en/api.html#events.downplay)
- `selectchanged` [](https://echarts.apache.org/en/api.html#events.selectchanged)
- `legendselectchanged` [](https://echarts.apache.org/en/api.html#events.legendselectchanged)
- `legendselected` [](https://echarts.apache.org/en/api.html#events.legendselected)
- `legendunselected` [](https://echarts.apache.org/en/api.html#events.legendunselected)
- `legendselectall` [](https://echarts.apache.org/en/api.html#events.legendselectall)
- `legendinverseselect` [](https://echarts.apache.org/en/api.html#events.legendinverseselect)
- `legendscroll` [](https://echarts.apache.org/en/api.html#events.legendscroll)
- `datazoom` [](https://echarts.apache.org/en/api.html#events.datazoom)
- `datarangeselected` [](https://echarts.apache.org/en/api.html#events.datarangeselected)
- `timelinechanged` [](https://echarts.apache.org/en/api.html#events.timelinechanged)
- `timelineplaychanged` [](https://echarts.apache.org/en/api.html#events.timelineplaychanged)
- `restore` [](https://echarts.apache.org/en/api.html#events.restore)
- `dataviewchanged` [](https://echarts.apache.org/en/api.html#events.dataviewchanged)
- `magictypechanged` [](https://echarts.apache.org/en/api.html#events.magictypechanged)
- `geoselectchanged` [](https://echarts.apache.org/en/api.html#events.geoselectchanged)
- `geoselected` [](https://echarts.apache.org/en/api.html#events.geoselected)
- `geounselected` [](https://echarts.apache.org/en/api.html#events.geounselected)
- `axisareaselected` [](https://echarts.apache.org/en/api.html#events.axisareaselected)
- `brush` [](https://echarts.apache.org/en/api.html#events.brush)
- `brushEnd` [](https://echarts.apache.org/en/api.html#events.brushEnd)
- `brushselected` [](https://echarts.apache.org/en/api.html#events.brushselected)
- `globalcursortaken` [](https://echarts.apache.org/en/api.html#events.globalcursortaken)
- `rendered` [](https://echarts.apache.org/en/api.html#events.rendered)
- `finished` [](https://echarts.apache.org/en/api.html#events.finished)
- Mouse events
- `click` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.click)
- `dblclick` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.dblclick)
- `mouseover` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseover)
- `mouseout` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseout)
- `mousemove` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mousemove)
- `mousedown` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mousedown)
- `mouseup` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseup)
- `globalout` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.globalout)
- `contextmenu` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.contextmenu)
- ZRender events
- `zr:click`
- `zr:mousedown`
- `zr:mouseup`
- `zr:mousewheel`
- `zr:dblclick`
- `zr:contextmenu`
See supported events [here →](https://echarts.apache.org/en/api.html#events)
#### Native DOM Events
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 (or you can use the `.native` modifier in Vue 2, which is dropped in Vue 3).
```vue
<template>
<v-chart @native:click="handleClick" />
</template>
```
### 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:
@ -391,104 +450,23 @@ import { THEME_KEY } from 'vue-echarts'
Static methods can be accessed from [`echarts` itself](https://echarts.apache.org/en/api.html#echarts).
### Events
## CSP: `style-src` or `style-src-elem`
You can bind events with Vue's `v-on` directive.
If you are applying a CSP to prevent inline `<style>` injection, you need to use `vue-echarts/csp` instead of `vue-echarts` and include `vue-echarts/csp/style.css` manually.
```vue
<template>
<v-chart :option="option" @highlight="handleHighlight" />
</template>
```
## Migration to v7
> **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:
- `highlight` [](https://echarts.apache.org/en/api.html#events.highlight)
- `downplay` [](https://echarts.apache.org/en/api.html#events.downplay)
- `selectchanged` [](https://echarts.apache.org/en/api.html#events.selectchanged)
- `legendselectchanged` [](https://echarts.apache.org/en/api.html#events.legendselectchanged)
- `legendselected` [](https://echarts.apache.org/en/api.html#events.legendselected)
- `legendunselected` [](https://echarts.apache.org/en/api.html#events.legendunselected)
- `legendselectall` [](https://echarts.apache.org/en/api.html#events.legendselectall)
- `legendinverseselect` [](https://echarts.apache.org/en/api.html#events.legendinverseselect)
- `legendscroll` [](https://echarts.apache.org/en/api.html#events.legendscroll)
- `datazoom` [](https://echarts.apache.org/en/api.html#events.datazoom)
- `datarangeselected` [](https://echarts.apache.org/en/api.html#events.datarangeselected)
- `timelinechanged` [](https://echarts.apache.org/en/api.html#events.timelinechanged)
- `timelineplaychanged` [](https://echarts.apache.org/en/api.html#events.timelineplaychanged)
- `restore` [](https://echarts.apache.org/en/api.html#events.restore)
- `dataviewchanged` [](https://echarts.apache.org/en/api.html#events.dataviewchanged)
- `magictypechanged` [](https://echarts.apache.org/en/api.html#events.magictypechanged)
- `geoselectchanged` [](https://echarts.apache.org/en/api.html#events.geoselectchanged)
- `geoselected` [](https://echarts.apache.org/en/api.html#events.geoselected)
- `geounselected` [](https://echarts.apache.org/en/api.html#events.geounselected)
- `axisareaselected` [](https://echarts.apache.org/en/api.html#events.axisareaselected)
- `brush` [](https://echarts.apache.org/en/api.html#events.brush)
- `brushEnd` [](https://echarts.apache.org/en/api.html#events.brushEnd)
- `brushselected` [](https://echarts.apache.org/en/api.html#events.brushselected)
- `globalcursortaken` [](https://echarts.apache.org/en/api.html#events.globalcursortaken)
- `rendered` [](https://echarts.apache.org/en/api.html#events.rendered)
- `finished` [](https://echarts.apache.org/en/api.html#events.finished)
- Mouse events
- `click` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.click)
- `dblclick` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.dblclick)
- `mouseover` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseover)
- `mouseout` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseout)
- `mousemove` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mousemove)
- `mousedown` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mousedown)
- `mouseup` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.mouseup)
- `globalout` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.globalout)
- `contextmenu` [](https://echarts.apache.org/en/api.html#events.Mouse%20events.contextmenu)
- ZRender events
- `zr:click`
- `zr:mousedown`
- `zr:mouseup`
- `zr:mousewheel`
- `zr:dblclick`
- `zr:contextmenu`
See supported events [here →](https://echarts.apache.org/en/api.html#events)
## Migration to v6
> 💡 Please make sure to read the [migration guide](https://echarts.apache.org/en/tutorial.html#ECharts%205%20Upgrade%20Guide) for ECharts 5 as well.
The following breaking changes are introduced in `vue-echarts@6`:
### Vue 2 support
- 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.
### Props
- `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.
### 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.
### Computed getters
- Computed getters (`width`, `height`, `isDisposed` and `computedOptions`) are removed. Use the **`getWidth`, `getHeight`, `isDisposed` and `getOption`** methods instead.
### Styles
- Now the root element of the component have **`100%×100%`** size by default, instead of `600×400`.
Read the breaking changes document in the [release log](https://github.com/ecomfe/vue-echarts/releases/tag/v7.0.0-beta.0) and the migration shoud be straightforward.
## Local development
```bash
$ npm i
$ npm run serve
```sh
pnpm i
pnpm serve
```
Open `http://localhost:8080` 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,37 +1,21 @@
# Vue-ECharts
<h1 align="center">Vue-ECharts</h1>
> Apache ECharts 的 Vue.js 组件。
<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>
使用 [Apache ECharts](https://echarts.apache.org/zh/index.html) 5同时支持 [Vue.js](https://vuejs.org/) 2/3。
---
## 💡 注意 💡
若您准备从 `vue-echarts` ≤ 5 的版本迁移到新版本,请在升级 v6 前阅读 _[迁移到 v6](#迁移到-v6)_ 部分文档。
没准备好的话,可以继续阅读老版本的文档。[前往 →](https://github.com/ecomfe/vue-echarts/blob/5.x/README.zh_CN.md)
> 还在使用 v6可以继续阅读老版本的文档。[前往 →](https://github.com/ecomfe/vue-echarts/blob/6.x/README.zh_CN.md)
## 安装 & 使用
### npm & ESM
```bash
$ npm install echarts vue-echarts
```
要在 _Vue 2_<2.7.0下使用 `vue-echarts`需要确保 `@vue/composition-api` 已经安装
### npm
```sh
npm i -D @vue/composition-api
npm add echarts vue-echarts
```
如果你在使用基于 _Vue 2_<2.7.0 _NuxtJS_那么还需要安装 `@nuxtjs/composition-api`
```sh
npm i -D @nuxtjs/composition-api
```
然后在 `nuxt.config.js` `buildModules` 选项中添加 `'@nuxtjs/composition-api/module'`
#### 示例
<details>
@ -206,7 +190,12 @@ export default {
</details>
为了更小的打包体积,我们建议手动从 ECharts 引入单个图表和组件。请参考所有支持的渲染器/图表/组件。[前往 →](https://github.com/apache/echarts/blob/master/src/echarts.all.ts)
> [!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 从而无需手动引入模块,只需要在代码中添加:
@ -214,7 +203,7 @@ export default {
import "echarts";
```
### CDN & 全局变量
### CDN
用如下方式在 HTML 中插入 `<script>` 标签,并且通过 `window.VueECharts` 来访问组件接口:
@ -223,9 +212,9 @@ import "echarts";
<!-- vue3Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.37"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.33"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@7.0.0"></script>
```
<!-- vue3Scripts:end -->
@ -243,9 +232,9 @@ app.component('v-chart', VueECharts)
<!-- vue2Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.5"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.3.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@7.0.0"></script>
```
<!-- vue2Scripts:end -->
@ -288,9 +277,9 @@ Vue.component("v-chart", VueECharts);
图表的分组,用于[联动](https://echarts.apache.org/zh/api.html#echarts.connect)。请参考 `echartsInstance.group`。[前往 →](https://echarts.apache.org/zh/api.html#echartsInstance.group)
- `autoresize: boolean`(默认值`false`
- `autoresize: boolean | { throttle?: number, onResize?: () => void }`(默认值`false`
图表在组件根元素尺寸变化时是否需要自动进行重绘。
图表在组件根元素尺寸变化时是否需要自动进行重绘。也可以传入一个选项对象来指定自定义的节流延迟和尺寸变化时的额外回调函数。
- `loading: boolean`(默认值:`false`
@ -368,6 +357,16 @@ Vue-ECharts 支持如下事件:
请参考支持的事件列表。[前往 →](https://echarts.apache.org/zh/api.html#events)
#### 原生 DOM 事件
由于 Vue-ECharts 默认将事件绑定到 ECharts 实例,因此在使用原生 DOM 事件时需要做一些特殊处理。你需要在事件名称前加上 `native:` 前缀来绑定原生 DOM 事件(可以在 Vue 2 中也可以使用 `.native` 修饰符,但这在 Vue 3 中已被废弃)。
```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` 提供上下文配置:
@ -451,42 +450,28 @@ import { THEME_KEY } from 'vue-echarts'
静态方法请直接通过 [`echarts` 本身](https://echarts.apache.org/zh/api.html#echarts)进行调用。
## 迁移到 v6
> 💡 请确保同时查阅 ECharts 5 的[升级指南](https://echarts.apache.org/zh/tutorial.html#ECharts%205%20%E5%8D%87%E7%BA%A7%E6%8C%87%E5%8D%97)。
`vue-echarts@6` 引入了如下破坏性变更:
## CSP: `style-src` 或 `style-src-elem`
### Vue 2 支持
如果你正在应用 CSP 来防止内联 `<style>` 注入,则需要使用 `vue-echarts/csp` 代替 `vue-echarts`,并手动引入 `vue-echarts/csp/style.css`
- 要在 `vue@2.7.0` 之前的版本中使用 Vue-ECharts必须安装 `@vue/composition-api`
## 迁移到 v7
### Prop
Translate:
Read the breaking changes document in the [release log](https://github.com/ecomfe/vue-echarts/releases/tag/v7.0.0-beta.0) and the migration shoud be straightforward.
- `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`
请阅读[发布日志](https://github.com/ecomfe/vue-echarts/releases/tag/v7.0.0-beta.0)中的变更记录,之后迁移过程应该会相对简单
## 本地开发
```bash
$ npm i
$ npm run serve
```sh
pnpm i
pnpm serve
```
打开 `http://localhost:8080` 来查看 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"]
};

3
babel.config.json Normal file
View File

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

View File

@ -1,82 +1,90 @@
{
"name": "vue-echarts",
"version": "6.4.1",
"description": "Vue.js component for Apache ECharts.",
"version": "7.0.1",
"description": "Vue.js component for Apache ECharts.",
"license": "MIT",
"repository": "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",
"build": "pnpm run docs && rimraf dist && pnpm run build:rollup",
"build:rollup": "vue-demi-switch 3 && rollup -c rollup.config.js",
"lint": "vue-cli-service lint",
"publint": "publint",
"build:demo": "vue-cli-service build",
"docs": "node -r esm ./scripts/docs.js",
"postinstall": "node ./scripts/postinstall.js",
"prepare": "pnpm run build"
"docs": "node ./scripts/docs.mjs",
"prepublishOnly": "pnpm run build && publint"
},
"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",
"./csp": "./dist/csp/index.js",
"./csp/style.css": "./dist/csp/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"
],
"dependencies": {
"resize-detector": "^0.3.0",
"vue-demi": "^0.13.2"
},
"devDependencies": {
"@babel/core": "^7.17.10",
"@rollup/plugin-node-resolve": "^11.1.1",
"@typescript-eslint/eslint-plugin": "^4.15.1",
"@typescript-eslint/parser": "^4.15.1",
"@vue/cli-plugin-babel": "^5.0.4",
"@vue/cli-plugin-eslint": "^5.0.4",
"@vue/cli-plugin-typescript": "^5.0.4",
"@vue/cli-service": "^5.0.4",
"@vue/compiler-sfc": "^3.2.33",
"@vue/composition-api": "^1.7.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"comment-mark": "^1.0.0",
"core-js": "^3.23.0",
"echarts": "^5.3.2",
"echarts-liquidfill": "^3.1.0",
"eslint": "^7.20.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^8.7.1",
"esm": "^3.2.25",
"postcss": "^8.3.0",
"postcss-loader": "^5.0.0",
"postcss-nested": "^5.0.5",
"prettier": "^2.6.2",
"qs": "^6.10.5",
"raw-loader": "^4.0.2",
"resize-detector": "^0.3.0",
"rimraf": "^3.0.2",
"rollup": "^2.72.1",
"rollup-plugin-dts": "^4.2.1",
"rollup-plugin-styles": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-ts": "^2.0.7",
"tslib": "^2.4.0",
"typescript": "4.6.4",
"vue": "^3.2.33",
"vue2": "npm:vue@^2.7.14",
"webpack": "^5.72.1"
"vue-demi": "^0.13.11"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.5",
"echarts": "^5.1.2",
"vue": "^2.6.12 || ^3.1.1"
"@vue/runtime-core": "^3.0.0",
"echarts": "^5.5.1",
"vue": "^2.7.0 || ^3.1.1"
},
"jsdelivr": "dist/index.umd.min.js",
"license": "MIT",
"peerDependenciesMeta": {
"@vue/composition-api": {
"@vue/runtime-core": {
"optional": true
}
},
"repository": "https://github.com/ecomfe/vue-echarts.git",
"types": "dist/index.d.ts"
"devDependencies": {
"@babel/core": "^7.24.9",
"@highlightjs/vue-plugin": "^2.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.7",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"@vercel/analytics": "^1.3.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.4.33",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vueuse/core": "^10.11.0",
"comment-mark": "^1.1.1",
"core-js": "^3.37.1",
"echarts": "^5.5.1",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0",
"esbuild-wasm": "^0.23.0",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.27.0",
"highlight.js": "^11.10.0",
"pinia": "^2.1.7",
"postcss": "^8.4.39",
"postcss-loader": "^8.1.1",
"postcss-nested": "^6.2.0",
"prettier": "^3.3.3",
"publint": "^0.2.9",
"raw-loader": "^4.0.2",
"resize-detector": "^0.3.0",
"rimraf": "^6.0.1",
"rollup": "^4.19.0",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-import-css": "^3.5.0",
"tslib": "^2.6.3",
"typescript": "5.5.4",
"vue": "^3.4.33",
"vue2": "npm:vue@^2.7.16",
"webpack": "^5.93.0"
}
}

12759
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
<!DOCTYPE html>
<html lang="">
<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="<%= 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>
<title>Vue-ECharts: Vue.js component for Apache ECharts.</title>
</head>
<body>
<noscript>

View File

@ -1,75 +1,60 @@
import typescript from "rollup-plugin-ts";
import { terser } from "rollup-plugin-terser";
import resolve from "@rollup/plugin-node-resolve";
import styles from "rollup-plugin-styles";
import { injectVueDemi } from "./scripts/rollup";
import replace from "@rollup/plugin-replace";
import esbuild from "rollup-plugin-esbuild";
import { dts } from "rollup-plugin-dts";
import css from "rollup-plugin-import-css";
import { injectVueDemi } from "./scripts/rollup.mjs";
/**
* 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 ? css({ output: "style.css" }) : css({ inject: true })
];
// 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 options = [
const builds = [
{
input: "src/index.ts",
plugins: [
typescript({
tsconfig: resolvedConfig => ({ ...resolvedConfig, declaration: true }),
hook: {
outputPath: (path, kind) =>
kind === "declaration" ? "dist/index.d.ts" : path
}
}),
styles()
],
external: ["vue-demi", "echarts/core", "resize-detector"],
output: {
file: "dist/index.esm.js",
format: "esm",
sourcemap: true
}
},
{
input: "src/index.ts",
plugins: [typescript(), styles()],
external: ["vue-demi", "echarts/core", "resize-detector"],
plugins: [esbuild()],
external: ["vue-demi", /^echarts/],
output: [
{
file: "dist/index.esm.min.js",
file: "dist/index.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(), styles()],
external: ["vue-demi", "echarts", "echarts/core"],
plugins: [esbuild({ minify: true })],
external: ["vue-demi", /^echarts/],
output: [
{
file: "dist/index.umd.js",
file: "dist/index.min.js", // for unpkg/jsdelivr
format: "umd",
name: "VueECharts",
exports: "default",
@ -80,29 +65,26 @@ const options = [
"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 options;
export default [
...builds.map(options => configBuild(options, false)),
...builds.map(options => configBuild(options, true)),
{
input: "src/index.d.ts",
plugins: [dts()],
output: [
{
file: "dist/index.d.ts",
format: "esm"
},
{
file: "dist/csp/index.d.ts",
format: "esm"
}
]
}
];

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;

View File

@ -1,16 +1,15 @@
import fs from "fs";
import { resolve } from "path";
import { readFileSync, writeFileSync } from "node:fs";
import commentMark from "comment-mark";
import { name, version } from "../package.json";
import { getPackageMeta, resolvePath } from "./utils.mjs";
const { readFile, writeFile } = fs.promises;
const { name, version } = getPackageMeta();
const CDN_PREFIX = "https://cdn.jsdelivr.net/npm/";
const DEP_VERSIONS = {
"vue@3": "3.2.37",
"vue@2": "2.7.5",
echarts: "5.3.3",
"vue@3": "3.4.33",
"vue@2": "2.7.16",
echarts: "5.5.1",
[name]: version
};
@ -39,29 +38,20 @@ const scripts = {
};
const README_FILES = ["README.md", "README.zh-Hans.md"].map(name =>
resolve(__dirname, "..", name)
resolvePath(import.meta.url, "..", name)
);
function exec() {
return Promise.all(
README_FILES.map(async file => {
const content = await readFile(file, "utf8");
README_FILES.forEach(file => {
const content = readFileSync(file, "utf8");
return writeFile(
file,
commentMark(content, {
vue2Scripts: getCodeBlock(scripts[2]),
vue3Scripts: getCodeBlock(scripts[3])
}),
"utf8"
);
})
writeFileSync(
file,
commentMark(content, {
vue2Scripts: getCodeBlock(scripts[2]),
vue3Scripts: getCodeBlock(scripts[3])
}),
"utf8"
);
}
});
async function main() {
await exec();
console.log("README files updated.");
}
main();
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;
}
};

17
scripts/rollup.mjs Normal file
View File

@ -0,0 +1,17 @@
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
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;`;
}
};

View File

@ -1,6 +0,0 @@
{
"globals": {
"Vue": true,
"VueECharts": true
}
}

View File

@ -1,3 +0,0 @@
#app {
height: 400px;
}

View File

@ -1,3 +0,0 @@
<div id="app">
<v-chart autoresize :option="option"/>
</div>

View File

@ -1,55 +0,0 @@
Vue.component("v-chart", VueECharts);
new Vue({
el: "#app",
data() {
return {
option: {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
},
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)"
}
}
}
]
}
};
}
});

View File

@ -1,54 +0,0 @@
Vue.createApp({
data() {
return {
option: {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
},
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)"
}
}
}
]
}
};
}
})
.component("v-chart", VueECharts)
.mount("#app");

13
scripts/utils.mjs Normal file
View File

@ -0,0 +1,13 @@
import { readFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
export function resolvePath(url, ...parts) {
return resolve(dirname(fileURLToPath(url)), ...parts);
}
export function getPackageMeta() {
return JSON.parse(
readFileSync(resolvePath(import.meta.url, "../package.json"), "utf8")
);
}

View File

@ -13,11 +13,21 @@ import {
nextTick,
watchEffect,
getCurrentInstance,
Vue2,
type PropType,
type InjectionKey
Vue2
} from "vue-demi";
import { init as initChart } from "echarts/core";
import {
usePublicAPI,
useAutoresize,
autoresizeProps,
useLoading,
loadingProps
} from "./composables";
import { isOn, omitOn, unwrapInjected } from "./utils";
import { register, TAG_NAME } from "./wc";
import type { PropType, InjectionKey } from "vue-demi";
import type {
EChartsType,
EventTarget,
@ -30,18 +40,12 @@ import type {
UpdateOptionsInjection,
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 type { EChartsElement } from "./wc";
import "./style.css";
const wcRegistered = register();
const __CSP__ = false;
const wcRegistered = __CSP__ ? false : register();
if (Vue2) {
Vue2.config.ignoredElements.push(TAG_NAME);
@ -54,6 +58,8 @@ export const UPDATE_OPTIONS_KEY =
"ecUpdateOptions" as unknown as InjectionKey<UpdateOptionsInjection>;
export { LOADING_OPTIONS_KEY } from "./composables";
const NATIVE_EVENT_RE = /(^&?~?!?)native:/;
export default defineComponent({
name: "echarts",
props: {
@ -68,7 +74,7 @@ export default defineComponent({
...autoresizeProps,
...loadingProps
},
emits: [] as unknown as Emits,
emits: {} as unknown as Emits,
inheritAttrs: false,
setup(props, { attrs }) {
const root = shallowRef<EChartsElement>();
@ -93,9 +99,62 @@ export default defineComponent({
() => props.updateOptions || unwrapInjected(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 realListeners: Record<string, any> = {};
if (!listeners) {
// This is for Vue 3.
// We are converting all `on<Event>` props to event listeners compatible with Vue 2
// and collect them into `realListeners` so that we can bind them to the chart instance
// later in the same way.
// 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 => {
// onClick -> c + lick
// onZr:click -> z + r:click
let event = key.charAt(2).toLowerCase() + key.slice(3);
// Collect native DOM events
if (event.indexOf("native:") === 0) {
// native:click -> onClick
const nativeKey = `on${event.charAt(7).toUpperCase()}${event.slice(
8
)}`;
nativeListeners[nativeKey] = attrs[key];
return;
}
// clickOnce -> ~click
// zr:clickOnce -> ~zr:click
if (event.substring(event.length - 4) === "Once") {
event = `~${event.substring(0, event.length - 4)}`;
}
realListeners[event] = attrs[key];
});
} else {
// This is for Vue 2.
// We just need to distinguish normal events and `native:<event>` events and
// collect them into `realListeners` and `nativeListeners` respectively.
// For `native:<event>` events, we just strip the `native:` part and collect them
// into `nativeListeners` so that we can bind them to the root element directly.
// native:click -> click
// ~native:click -> ~click
// &~!native:click -> &~!click
Object.keys(listeners).forEach(key => {
if (NATIVE_EVENT_RE.test(key)) {
nativeListeners[key.replace(NATIVE_EVENT_RE, "$1")] = listeners[key];
} else {
realListeners[key] = listeners[key];
}
});
}
function init(option?: Option) {
if (!root.value) {
@ -112,27 +171,6 @@ export default defineComponent({
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];
@ -233,7 +271,9 @@ export default defineComponent({
init();
} else {
chart.value.setOption(option, {
notMerge: option.value !== oldOption?.value,
// mutating `option` will lead to `notMerge: false` and
// replacing it with new reference will lead to `notMerge: true`
notMerge: option !== oldOption,
...realUpdateOptions.value
});
}
@ -291,6 +331,7 @@ export default defineComponent({
root,
setOption,
nonEventAttrs,
nativeListeners,
...publicApi
};
},
@ -298,7 +339,9 @@ export default defineComponent({
// 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 }
Vue2
? { attrs: this.nonEventAttrs, on: this.nativeListeners }
: { ...this.nonEventAttrs, ...this.nativeListeners }
) as any;
attrs.ref = "root";
attrs.class = attrs.class ? ["echarts"].concat(attrs.class) : "echarts";

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Ref } from "vue-demi";
import { EChartsType } from "../types";
import type { Ref } from "vue-demi";
import type { EChartsType } from "../types";
const METHOD_NAMES = [
"getWidth",
@ -20,7 +20,7 @@ const METHOD_NAMES = [
"dispose"
] as const;
type MethodName = typeof METHOD_NAMES[number];
type MethodName = (typeof METHOD_NAMES)[number];
type PublicMethods = Pick<EChartsType, MethodName>;

View File

@ -1,32 +1,60 @@
import { Ref, watch } from "vue-demi";
import { watch } from "vue-demi";
import { throttle } from "echarts/core";
import { addListener, removeListener, ResizeCallback } from "resize-detector";
import { EChartsType } from "../types";
import type { Ref, PropType } from "vue-demi";
import type { EChartsType, AutoResize } from "../types";
export function useAutoresize(
chart: Ref<EChartsType | undefined>,
autoresize: Ref<boolean>,
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) {
resizeListener = throttle(() => {
chart.resize();
}, 100);
if (root && chart && autoresize) {
const { offsetWidth, offsetHeight } = root;
const autoresizeOptions = autoresize === true ? {} : autoresize;
const { throttle: wait = 100, onResize } = autoresizeOptions;
addListener(root, resizeListener);
}
let initialResizeTriggered = false;
cleanup(() => {
if (resizeListener && root) {
removeListener(root, resizeListener);
const callback = () => {
chart.resize();
onResize?.();
};
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;
}
}
resizeCallback();
});
ro.observe(root);
}
});
});
onCleanup(() => {
if (ro) {
ro.disconnect();
ro = null;
}
});
}
);
}
export const autoresizeProps = {
autoresize: Boolean
autoresize: [Boolean, Object] as PropType<AutoResize>
};

View File

@ -1,24 +1,18 @@
import { unwrapInjected } from "../utils";
import {
inject,
computed,
watchEffect,
type Ref,
type InjectionKey
} from "vue-demi";
import { EChartsType } from "../types";
import { inject, computed, watchEffect } from "vue-demi";
import type { Ref, InjectionKey, PropType } from "vue-demi";
import type { EChartsType, LoadingOptions } from "../types";
export const LOADING_OPTIONS_KEY =
"ecLoadingOptions" as unknown as InjectionKey<
UnknownRecord | Ref<UnknownRecord>
LoadingOptions | Ref<LoadingOptions>
>;
type UnknownRecord = Record<string, unknown>;
export function useLoading(
chart: Ref<EChartsType | undefined>,
loading: Ref<boolean>,
loadingOptions: Ref<UnknownRecord | undefined>
loadingOptions: Ref<LoadingOptions | undefined>
): void {
const defaultLoadingOptions = inject(LOADING_OPTIONS_KEY, {});
const realLoadingOptions = computed(() => ({
@ -42,5 +36,5 @@ export function useLoading(
export const loadingProps = {
loading: Boolean,
loadingOptions: Object
loadingOptions: Object as PropType<LoadingOptions>
};

425
src/demo/CodeGen.vue Normal file
View File

@ -0,0 +1,425 @@
<script setup>
import {
ref,
computed,
watch,
onBeforeUnmount,
defineProps,
defineEmits,
onMounted,
nextTick
} from "vue";
import { useLocalStorage } from "@vueuse/core";
import "highlight.js/styles/github.css";
import hljs from "highlight.js/lib/core";
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";
import { track } from "@vercel/analytics";
import { getImportsFromOption } from "./utils/codegen";
hljs.registerLanguage("javascript", javascript);
hljs.registerLanguage("typescript", typescript);
const CodeHighlight = hljsVuePlugin.component;
const codegenOptions = useLocalStorage("ve.codegenOptions", {
indent: " ",
quote: "'",
multiline: false,
maxLen: 80,
semi: false,
includeType: false
});
const props = defineProps({ open: Boolean, renderer: String });
const emit = defineEmits(["update:open"]);
const dialog = ref(null);
let clickFrom = null;
function closeFromOutside() {
if (dialog.value?.contains(clickFrom)) {
return;
}
close();
}
function close() {
emit("update:open", false);
}
const renderer = ref(props.renderer);
const source = ref(null);
watch(
() => props.open,
async val => {
if (val) {
renderer.value = props.renderer;
}
await nextTick();
if (initializing.value) {
return;
}
source.value?.focus();
}
);
const copied = ref(false);
const initializing = ref(true);
const optionCode = ref("");
const transformedCode = ref("");
const transformErrors = ref([]);
onMounted(async () => {
await initialize({ wasmURL });
initializing.value = false;
optionCode.value = `{
title: {
text: 'Referer of a Website',
subtext: 'Fake Data',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}`;
await nextTick();
source.value?.focus();
});
watch(optionCode, async val => {
try {
transformedCode.value = await transform(`(${val})`, { loader: "ts" });
transformErrors.value = [];
} catch (e) {
transformErrors.value = e.errors;
}
});
function formatError(errors) {
return errors
.map(({ text, location: { lineText, line, column, length } }) => {
const digit = Math.ceil(Math.log10(line)) || 1;
lineText = line === 1 ? lineText.slice(1) : lineText;
lineText =
line === optionCode.value.split("\n").length
? lineText.slice(0, -1)
: lineText;
column = line === 1 ? column - 1 : column;
return `/* ${text} */
// ${line} | ${lineText}
// ${" ".repeat(digit)} | ${" ".repeat(column)}${"~".repeat(length)}
`;
})
.join("\n\n");
}
const importCode = computed(() => {
if (optionCode.value.trim() === "") {
return "// Paste your option code first";
}
if (transformErrors.value.length) {
return formatError(transformErrors.value);
}
try {
return getImportsFromOption(eval(transformedCode.value.code), {
renderer: renderer.value,
...codegenOptions.value
});
} catch (e) {
return `/* Invalid ECharts option */
// ${e.message}
`;
}
});
watch(importCode, () => {
copied.value = false;
});
// copy message
const messageOpen = ref(false);
let messageTimer;
function trackCopy(from) {
if (copied.value) {
// only track copy after modifications
return;
}
copied.value = true;
track("copy-code", { from });
}
function copy() {
trackCopy("button");
clearTimeout(messageTimer);
navigator.clipboard.writeText(importCode.value);
messageOpen.value = true;
messageTimer = setTimeout(() => {
messageOpen.value = false;
}, 2018);
}
onBeforeUnmount(() => {
clearTimeout(messageTimer);
});
</script>
<template>
<aside
class="modal"
:class="{ open: props.open }"
@mousedown="clickFrom = $event.target"
@click="closeFromOutside"
@keydown.esc="close"
>
<section class="dialog" ref="dialog">
<h2> <code>import</code> code generator</h2>
<section class="options">
<label>
Renderer
<select v-model="renderer">
<option value="canvas">Canvas</option>
<option value="svg">SVG</option>
</select>
</label>
<label>
TypeScript
<input type="checkbox" v-model="codegenOptions.includeType" />
</label>
<label>
Multiline
<input type="checkbox" v-model="codegenOptions.multiline" />
</label>
<label>
Semi
<input type="checkbox" v-model="codegenOptions.semi" />
</label>
<label>
Quote
<select v-model="codegenOptions.quote">
<option value="'">single</option>
<option value='"'>double</option>
</select>
</label>
<label>
Indent
<select v-model="codegenOptions.indent">
<option value=" ">2</option>
<option value=" ">4</option>
<option value=" ">Tab</option>
</select>
</label>
<label>
Max length
<input
type="number"
step="10"
v-model.number="codegenOptions.maxLen"
/>
</label>
</section>
<section class="code">
<textarea
ref="source"
class="option-code"
v-model="optionCode"
:placeholder="
initializing
? 'Initializing...'
: 'Paste your option code (TS/JS literal) here...'
"
:disabled="initializing"
autofocus
spellcheck="false"
></textarea>
<div class="import-code" @copy="trackCopy('system')">
<code-highlight
:language="codegenOptions.includeType ? 'typescript' : 'javascript'"
:code="importCode"
/>
</div>
<button
class="copy"
@click="copy"
:disabled="importCode.startsWith('/*') || importCode.startsWith('//')"
>
Copy
</button>
</section>
</section>
</aside>
<aside class="message" :class="{ open: messageOpen }">
Copied to clipboard
</aside>
</template>
<style>
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Works for Firefox */
input[type="number"] {
appearance: textfield;
}
input[type="text"],
input[type="number"] {
cursor: text;
}
.dialog {
display: flex;
flex-direction: column;
width: 80vw;
height: 90vh;
border-radius: 6px;
overflow: hidden;
background-color: #fff;
box-shadow: 0 0 45px rgba(0, 0, 0, 0.2);
}
.options {
display: flex;
justify-content: center;
align-items: stretch;
gap: 16px;
padding: 16px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
label {
display: flex;
align-items: center;
gap: 4px;
}
input,
button,
select {
height: 2.4em;
}
input[type="number"] {
width: 60px;
}
}
.code {
position: relative;
display: flex;
justify-content: center;
align-items: stretch;
flex-grow: 1;
min-height: 0;
tab-size: 4;
.option-code,
.import-code {
flex: 0 0 50%;
margin: 0;
border: none;
line-height: 1.2;
font-size: 13px;
overflow: auto;
}
.import-code {
border-left: 1px solid rgba(0, 0, 0, 0.1);
pre {
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: #fff;
box-shadow: none;
}
code {
height: 100%;
}
}
.option-code {
padding: 1em;
outline: none;
resize: none;
}
}
.copy {
position: absolute;
right: 10px;
top: 10px;
}
.message {
position: fixed;
z-index: 2147483647;
bottom: 2rem;
left: 50%;
padding: 0.5rem 0.75rem;
background-color: rgba(45, 52, 64, 0.98);
box-shadow: 0 4px 16px rgba(45, 52, 64, 0.6);
color: #fff;
font-size: 0.875rem;
transform: translate(-50%, 200%);
border-radius: 4px;
opacity: 0;
transition:
transform 0.2s,
opacity 0.2s;
}
.message.open {
transform: translate(-50%, 0);
opacity: 1;
}
</style>

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 KiB

BIN
src/demo/assets/world.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -5,7 +5,8 @@ function random() {
export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
dataset: {
dimensions: ["Product", "2015", "2016", "2017"],

View File

@ -9,17 +9,22 @@ for (let i = 0; i < 16; i++) {
]);
}
export const c1 = {
const c1 = {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
legend: {
top: 20,
top: "3%",
data: ["scatter"]
},
tooltip: {
formatter: "{c}"
},
grid: {
top: "26%",
bottom: "26%"
top: "30%",
right: "18%",
bottom: "20%"
},
xAxis: {
type: "value",
@ -36,7 +41,8 @@ export const c1 = {
visualMap: [
{
realtime: false,
left: "right",
right: "2%",
bottom: "3%",
selectedMode: "multiple",
dimension: 2,
selected: [],
@ -57,17 +63,22 @@ export const c1 = {
]
};
export const c2 = {
const c2 = {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
legend: {
top: 20,
top: "3%",
data: ["scatter"]
},
tooltip: {
formatter: "{c}"
},
grid: {
top: "26%",
bottom: "26%"
top: "30%",
right: "18%",
bottom: "20%"
},
xAxis: {
type: "value",
@ -83,7 +94,8 @@ export const c2 = {
},
visualMap: [
{
left: "right",
right: "2%",
bottom: "3%",
selectedMode: "multiple",
dimension: 2,
selected: [],
@ -103,3 +115,7 @@ export const c2 = {
}
]
};
export default function getData() {
return [c1, c2];
}

View File

@ -1,6 +1,4 @@
/* eslint-disable */
import logo from '../assets/Vue-ECharts.svg'
/* eslint-enable */
import logo from "../assets/Vue-ECharts.svg";
const d = logo.match(/\bd="([^"]+)"/)[1];
@ -27,6 +25,10 @@ export default {
return "";
}
}
},
itemStyle: {
shadowBlur: 12,
shadowColor: "rgba(0, 0, 0, 0.25)"
}
}
]

View File

@ -397,92 +397,98 @@ function convertData(data) {
return res;
}
export default {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
},
backgroundColor: "#404a59",
title: {
text: "Air quality of major cities in China",
subtext: "data from PM25.in",
sublink: "http://www.pm25.in",
left: "center",
export default function getData() {
return {
textStyle: {
color: "#fff"
}
},
tooltip: {
trigger: "item"
},
legend: {
orient: "vertical",
y: "bottom",
x: "right",
data: ["pm2.5"],
textStyle: {
color: "#fff"
}
},
geo: {
map: "china",
emphasis: {
label: {
show: false
},
itemStyle: {
areaColor: "#2a333d"
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
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"
}
},
itemStyle: {
areaColor: "#323c48",
borderColor: "#111"
}
},
series: [
{
name: "pm2.5",
type: "scatter",
coordinateSystem: "geo",
data: convertData(data),
symbolSize: val => val[2] / 10,
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
itemStyle: {
color: "#ddb926"
tooltip: {
trigger: "item"
},
legend: {
orient: "vertical",
right: "5%",
bottom: "5%",
data: ["PM2.5"],
textStyle: {
color: "#fff"
}
},
{
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,
showEffectOn: "render",
rippleEffect: {
brushType: "stroke"
},
geo: {
map: "china",
emphasis: {
scale: true
},
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
label: {
show: false
},
itemStyle: {
areaColor: "#2a333d"
}
},
label: {
formatter: "{b}",
position: "right",
show: true
},
itemStyle: {
color: "#f4e925",
shadowBlur: 10,
shadowColor: "#333"
areaColor: "#323c48",
borderColor: "#111"
},
zlevel: 1
}
]
};
top: "20%",
bottom: "7%"
},
series: [
{
name: "PM2.5",
type: "scatter",
coordinateSystem: "geo",
data: convertData(data),
symbolSize: val => val[2] / 10,
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
itemStyle: {
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,
showEffectOn: "render",
rippleEffect: {
brushType: "stroke"
},
emphasis: {
scale: true
},
tooltip: {
formatter: function (val) {
return val.name + ": " + val.value[2];
}
},
label: {
formatter: "{b}",
position: "right",
show: true
},
itemStyle: {
color: "#f4e925",
shadowBlur: 10,
shadowColor: "#333"
},
zlevel: 1
}
]
};
}

View File

@ -1,40 +1,45 @@
export default {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
},
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)"
export default function getData() {
return {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
title: {
text: "Traffic Sources",
top: "5%",
left: "center"
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: "vertical",
top: "5%",
left: "5%",
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)"
}
}
}
}
]
};
]
};
}

View File

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

File diff suppressed because one or more lines are too long

69
src/demo/data/radar.ts Normal file
View File

@ -0,0 +1,69 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
export const useScoreStore = defineStore("store", () => {
const scores = ref([
{ name: "Attack", max: 20, value: 19 },
{ name: "Defense", max: 20, value: 9 },
{ name: "Speed", max: 20, value: 18 },
{ name: "Strength", max: 20, value: 16 },
{ name: "Endurance", max: 20, value: 16 },
{ name: "Agility", max: 20, value: 20 }
]);
const metrics = computed(() => {
return scores.value.map(({ name }) => name);
});
function getRadarData(activeIndex: number) {
return {
title: {
text: "Player Ability",
top: "5%",
left: "5%"
},
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
radar: {
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) }]
}
]
};
}
function increase(index: number, amount: number) {
const metric = scores.value[index];
metric.value = Math.max(Math.min(metric.value + amount, metric.max), 0);
}
function isMax(index: number) {
const { value, max } = scores.value[index];
return value === max;
}
function isMin(index: number) {
return scores.value[index].value === 0;
}
return {
scores,
metrics,
getRadarData,
increase,
isMax,
isMin
};
});

View File

@ -45,96 +45,105 @@ const data = [
]
];
export default {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
},
title: {
text: "Life Expectancy vs. GDP by country"
},
legend: {
right: 10,
data: ["1990", "2015"]
},
xAxis: {
splitLine: {
lineStyle: {
type: "dashed"
}
}
},
yAxis: {
splitLine: {
lineStyle: {
type: "dashed"
export default function getData() {
return {
grid: {
top: "25%"
},
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
title: {
text: "Life Expectancy vs. GDP by country",
top: "5%",
left: "5%"
},
legend: {
top: "6%",
right: "5%",
data: ["1990", "2015"]
},
xAxis: {
splitLine: {
lineStyle: {
type: "dashed"
}
}
},
scale: true
},
series: [
{
name: "1990",
data: data[0],
type: "scatter",
symbolSize(data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
label: {
show: true,
formatter({ data }) {
return data[3];
},
position: "top"
yAxis: {
splitLine: {
lineStyle: {
type: "dashed"
}
},
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(120, 36, 50, 0.5)",
shadowOffsetY: 5,
color: new graphic.RadialGradient(0.4, 0.3, 1, [
{
offset: 0,
color: "rgb(251, 118, 123)"
},
{
offset: 1,
color: "rgb(204, 46, 72)"
}
])
}
scale: true
},
{
name: "2015",
data: data[1],
type: "scatter",
symbolSize(data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
label: {
show: true,
formatter({ data }) {
return data[3];
},
position: "top"
series: [
{
name: "1990",
data: data[0],
type: "scatter",
symbolSize(data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
label: {
show: true,
formatter({ data }) {
return data[3];
},
position: "top"
}
},
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(120, 36, 50, 0.5)",
shadowOffsetY: 5,
color: new graphic.RadialGradient(0.4, 0.3, 1, [
{
offset: 0,
color: "rgb(251, 118, 123)"
},
{
offset: 1,
color: "rgb(204, 46, 72)"
}
])
}
},
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(25, 100, 150, 0.5)",
shadowOffsetY: 5,
color: new graphic.RadialGradient(0.4, 0.3, 1, [
{
offset: 0,
color: "rgb(129, 227, 238)"
},
{
offset: 1,
color: "rgb(25, 183, 207)"
{
name: "2015",
data: data[1],
type: "scatter",
symbolSize(data) {
return Math.sqrt(data[2]) / 5e2;
},
emphasis: {
label: {
show: true,
formatter({ data }) {
return data[3];
},
position: "top"
}
])
},
itemStyle: {
shadowBlur: 10,
shadowColor: "rgba(25, 100, 150, 0.5)",
shadowOffsetY: 5,
color: new graphic.RadialGradient(0.4, 0.3, 1, [
{
offset: 0,
color: "rgb(129, 227, 238)"
},
{
offset: 1,
color: "rgb(25, 183, 207)"
}
])
}
}
}
]
};
]
};
}

View File

@ -0,0 +1,77 @@
<script setup>
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 getData from "../data/bar";
import theme from "../theme.json";
use([BarChart, DatasetComponent, GridComponent]);
registerTheme("ovilia-green", theme);
const seconds = shallowRef(0);
const loading = shallowRef(false);
const loadingOptions = {
text: "Loading…",
color: "#4ea397",
maskColor: "rgba(255, 255, 255, 0.4)"
};
const option = shallowRef(getData());
let timer = null;
onBeforeUnmount(() => {
clearInterval(timer);
});
function tick() {
seconds.value--;
if (seconds.value === 0) {
clearInterval(timer);
loading.value = false;
option.value = getData();
}
}
function refresh() {
// simulating async data from server
seconds.value = 3;
loading.value = true;
timer = setInterval(tick, 1000);
}
</script>
<template>
<v-example
id="bar"
title="Bar chart"
desc="(with async data &amp; custom theme)"
>
<v-chart
:option="option"
theme="ovilia-green"
autoresize
:loading="loading"
:loadingOptions="loadingOptions"
/>
<template #extra>
<p v-if="seconds <= 0">
<small>Loaded.</small>
</p>
<p v-else>
<small>
Data coming in
<b>{{ seconds }}</b>
second{{ seconds > 1 ? "s" : "" }}...
</small>
</p>
<p class="actions">
<button @click="refresh" :disabled="seconds > 0">Refresh</button>
</p>
</template>
</v-example>
</template>

View File

@ -0,0 +1,54 @@
<script setup>
import { use, connect, disconnect } from "echarts/core";
import { ScatterChart } from "echarts/charts";
import {
GridComponent,
TitleComponent,
VisualMapComponent,
TooltipComponent
} from "echarts/components";
import { shallowRef, watch } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import getData from "../data/connect";
use([
ScatterChart,
GridComponent,
TitleComponent,
VisualMapComponent,
TooltipComponent
]);
const [c1, c2] = getData().map(shallowRef);
const connected = shallowRef(true);
watch(
connected,
value => {
if (value) {
connect("radiance");
} else {
disconnect("radiance");
}
},
{ immediate: true }
);
</script>
<template>
<v-example id="connect" title="Connectable charts" split>
<template #start>
<v-chart :option="c1" group="radiance" autoresize />
</template>
<template #end>
<v-chart :option="c2" group="radiance" autoresize />
</template>
<template #extra>
<p class="actions">
<input id="connected-check" type="checkbox" v-model="connected" />
<label for="connected-check">Connected</label>
</p>
</template>
</v-example>
</template>

View File

@ -0,0 +1,97 @@
<template>
<h3 :id="id">
<a :href="`#${id}`">
{{ title }}
<small v-if="desc">{{ desc }}</small>
</a>
</h3>
<section>
<figure class="fig hero" v-if="!split">
<slot />
</figure>
<div class="split" v-else>
<figure class="fig half">
<slot name="start" />
</figure>
<figure class="fig half">
<slot name="end" />
</figure>
</div>
<slot name="extra" />
</section>
</template>
<script setup>
import { defineProps } from "vue";
defineProps({
id: {
type: String,
required: true
},
title: {
type: String,
required: true
},
desc: String,
split: Boolean
});
</script>
<style>
.fig {
display: flex;
justify-content: center;
width: fit-content;
margin: 2em auto;
.echarts {
width: calc(60vw + 4em);
height: 360px;
max-width: 720px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
box-shadow: 0 0 45px rgba(0, 0, 0, 0.2);
}
}
.split {
display: flex;
justify-content: center;
.fig {
margin-right: 0;
margin-left: 0;
}
}
@media (max-width: 980px) {
.fig {
width: 100vw;
margin: 1em auto;
.echarts {
width: 100%;
min-width: 0;
height: 60vw;
border: none;
border-radius: 0;
box-shadow: none;
}
}
}
@media (min-width: 980px) {
.fig.half {
.echarts {
width: 28vw;
min-width: 240px;
height: 180px;
}
& + & {
margin-left: 30px;
}
}
}
</style>

View File

@ -0,0 +1,62 @@
<script setup>
import { use, registerMap } from "echarts/core";
import { ScatterChart, EffectScatterChart } from "echarts/charts";
import {
GeoComponent,
TitleComponent,
LegendComponent,
TooltipComponent
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import getData from "../data/map";
import chinaMap from "../china.json";
use([
ScatterChart,
EffectScatterChart,
GeoComponent,
TitleComponent,
LegendComponent,
TooltipComponent
]);
registerMap("china", chinaMap);
const option = shallowRef(getData());
const map = shallowRef(null);
const open = shallowRef(false);
let img = null;
function convert() {
img = {
src: map.value.getDataURL({
pixelRatio: window.devicePixelRatio || 1
}),
width: map.value.getWidth(),
height: map.value.getHeight()
};
open.value = true;
}
</script>
<template>
<v-example id="map" title="Map" desc="(with GeoJSON & image converter)">
<v-chart
ref="map"
:option="option"
autoresize
style="background-color: #404a59"
/>
<template #extra>
<p class="actions">
<button @click="convert">Convert to image</button>
</p>
<aside class="modal" :class="{ open }" @click="open = false">
<img v-if="img" v-bind="img" />
</aside>
</template>
</v-example>
</template>

View File

@ -0,0 +1,111 @@
<script setup>
import { use } from "echarts/core";
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 world from "../assets/world.jpg";
import starfield from "../assets/starfield.jpg";
use([Bar3DChart, VisualMapComponent, GlobeComponent]);
const option = shallowRef();
const loading = shallowRef(true);
const initOptions = {
renderer: "canvas"
};
const loadingOptions = {
text: "Loading...",
color: "#000",
textColor: "#fff",
maskColor: "transparent"
};
onMounted(() => {
import("../data/population.json").then(({ default: data }) => {
loading.value = false;
data = data
.filter(dataItem => dataItem[2] > 0)
.map(dataItem => [dataItem[0], dataItem[1], Math.sqrt(dataItem[2])]);
option.value = {
backgroundColor: "#000",
globe: {
baseTexture: world,
heightTexture: world,
shading: "lambert",
environment: starfield,
light: {
main: {
intensity: 2
}
},
viewControl: {
autoRotate: false
}
},
visualMap: {
bottom: "3%",
left: "3%",
max: 40,
calculable: true,
realtime: false,
inRange: {
colorLightness: [0.2, 0.9]
},
textStyle: {
color: "#fff"
},
controller: {
inRange: {
color: "orange"
}
},
outOfRange: {
colorAlpha: 0
}
},
series: [
{
type: "bar3D",
coordinateSystem: "globe",
data: data,
barSize: 0.6,
minHeight: 0.2,
silent: true,
itemStyle: {
color: "orange"
}
}
]
};
});
});
</script>
<template>
<v-example id="gl" title="GL charts" desc="(Globe & Bar3D)">
<v-chart
:option="option"
:init-options="initOptions"
autoresize
:loading="loading"
:loading-options="loadingOptions"
style="background-color: #000"
/>
<template #extra>
<p>
You can use extension packs like
<a href="https://github.com/ecomfe/echarts-gl">ECharts-GL</a>.
</p>
<p>
<small>(You can only use the canvas renderer for GL charts.)</small>
</p>
</template>
</v-example>
</template>

View File

@ -0,0 +1,14 @@
<script setup>
import { registerTheme } from "echarts/core";
import "echarts-liquidfill";
import VChart from "../../ECharts";
import theme from "../theme.json";
import logo from "../data/logo";
registerTheme("ovilia-green", theme);
</script>
<template>
<v-chart id="logo" :option="logo" />
</template>

View File

@ -0,0 +1,116 @@
<script setup>
import { use, registerMap } from "echarts/core";
import { LinesChart } from "echarts/charts";
import {
GeoComponent,
TitleComponent,
TooltipComponent
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import worldMap from "../world.json";
use([LinesChart, GeoComponent, TitleComponent, TooltipComponent]);
registerMap("world", worldMap);
const chart = shallowRef(null);
const loading = shallowRef(false);
const loaded = shallowRef(false);
const loadingOptions = {
text: "",
color: "#c23531",
textColor: "rgba(255, 255, 255, 0.5)",
maskColor: "#003",
zlevel: 0
};
function load() {
loaded.value = true;
loading.value = true;
import("../data/flight.json").then(({ default: data }) => {
loading.value = false;
function getAirportCoord(idx) {
return [data.airports[idx][3], data.airports[idx][4]];
}
const routes = data.routes.map(airline => {
return [getAirportCoord(airline[1]), getAirportCoord(airline[2])];
});
chart.value.setOption({
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif'
},
title: {
text: "World Flights",
top: "5%",
left: "center",
textStyle: {
color: "#eee"
}
},
backgroundColor: "#003",
tooltip: {
formatter(param) {
const route = data.routes[param.dataIndex];
return (
data.airports[route[1]][1] + " > " + data.airports[route[2]][1]
);
}
},
geo: {
map: "world",
top: "15%",
right: "5%",
bottom: "5%",
left: "5%",
silent: true,
itemStyle: {
borderColor: "#003",
color: "#005"
}
},
series: [
{
type: "lines",
coordinateSystem: "geo",
data: routes,
large: true,
largeThreshold: 100,
lineStyle: {
opacity: 0.05,
width: 0.5,
curveness: 0.3
},
blendMode: "lighter"
}
]
});
});
}
</script>
<template>
<v-example id="manual" title="Manual updates">
<v-chart
ref="chart"
autoresize
:loading="loading"
:loading-options="loadingOptions"
style="background-color: #003"
manual-update
/>
<template #extra>
<p>
You may use the <code>manual-update</code> prop for performance critical
use cases.
</p>
<p class="actions">
<button :disabled="loaded" @click="load">Load</button>
</p>
</template>
</v-example>
</template>

View File

@ -0,0 +1,82 @@
<script setup>
import { use } from "echarts/core";
import { PieChart } from "echarts/charts";
import {
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
} from "echarts/components";
import { shallowRef, onMounted } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import getData from "../data/pie";
use([
PieChart,
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
]);
const option = shallowRef(getData());
const pie = shallowRef(null);
let timer = null;
onMounted(() => {
startActions();
return () => {
stopActions();
};
});
function startActions() {
let dataIndex = -1;
const dataLen = option.value?.series?.[0]?.data?.length || 0;
if (!pie.value || dataLen === 0) {
return;
}
clearInterval(timer);
timer = setInterval(() => {
if (!pie.value) {
clearInterval(timer);
return;
}
pie.value.dispatchAction({
type: "downplay",
seriesIndex: 0,
dataIndex
});
dataIndex = (dataIndex + 1) % dataLen;
pie.value.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex
});
pie.value.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex
});
}, 1000);
}
function stopActions() {
clearInterval(timer);
}
</script>
<template>
<v-example id="pie" title="Pie chart" desc="(with action dispatch)">
<v-chart ref="pie" :option="option" autoresize />
</v-example>
</template>

View File

@ -0,0 +1,66 @@
<script setup>
import { use } from "echarts/core";
import { LineChart } from "echarts/charts";
import {
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
} from "echarts/components";
import { computed, shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import getData from "../data/polar";
use([
LineChart,
PolarComponent,
TitleComponent,
LegendComponent,
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>
<v-example id="polar" title="Polar plot" desc="(with built-in theme)">
<v-chart
:option="option"
autoresize
:loading="loading"
:loading-options="loadingOptions"
:theme="theme"
:style="style"
/>
<template #extra>
<p class="actions">
Theme
<select v-model="theme">
<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>
</template>

View File

@ -0,0 +1,50 @@
<script setup>
import { use } from "echarts/core";
import { RadarChart } from "echarts/charts";
import {
PolarComponent,
TitleComponent,
TooltipComponent
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import { useScoreStore } from "../data/radar";
use([RadarChart, PolarComponent, TitleComponent, TooltipComponent]);
const { metrics, getRadarData, increase, isMax, isMin } = useScoreStore();
const metricIndex = shallowRef(0);
</script>
<template>
<v-example id="radar" title="Radar chart" desc="(with Pinia integration)">
<v-chart :option="getRadarData(metricIndex)" autoresize />
<template #extra>
<p class="actions">
<select v-model="metricIndex">
<option
v-for="(metric, index) in metrics"
:value="index"
:key="index"
>
{{ metric }}
</option>
</select>
<button
@click="increase(metricIndex, 1)"
:disabled="isMax(metricIndex)"
>
Increase
</button>
<button
@click="increase(metricIndex, -1)"
:disabled="isMin(metricIndex)"
>
Decrease
</button>
</p>
</template>
</v-example>
</template>

View File

@ -0,0 +1,23 @@
<script setup>
import { use } from "echarts/core";
import { ScatterChart } from "echarts/charts";
import {
GridComponent,
TitleComponent,
LegendComponent
} from "echarts/components";
import { shallowRef } from "vue";
import VChart from "../../ECharts";
import VExample from "./Example";
import getData from "../data/scatter";
use([ScatterChart, GridComponent, TitleComponent, LegendComponent]);
const option = shallowRef(getData());
</script>
<template>
<v-example id="scatter" title="Scatter plot" desc="(with gradient)">
<v-chart :option="option" autoresize />
</v-example>
</template>

View File

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

View File

@ -1,4 +1,21 @@
import { inject } from "@vercel/analytics";
import { createApp } from "vue";
import { createPinia } from "pinia";
import Demo from "./Demo.vue";
createApp(Demo).mount("#app");
const SAMPLE_RATE = 0.5;
inject({
beforeSend: event => {
if (Math.random() > SAMPLE_RATE) {
return null;
}
return event;
}
});
const pinia = createPinia();
const app = createApp(Demo);
app.use(pinia);
app.mount("#app");

201
src/demo/utils/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

472
src/demo/utils/codegen.js Normal file
View File

@ -0,0 +1,472 @@
// Modified from https://github.com/apache/echarts-examples/blob/b644ced5325ea2522cb11606df54eae69bba3a3a/common/buildCode.js
// See license at `./LICENSE`.
const COMPONENTS_MAP = {
grid: "GridComponent",
polar: "PolarComponent",
geo: "GeoComponent",
singleAxis: "SingleAxisComponent",
parallel: "ParallelComponent",
calendar: "CalendarComponent",
graphic: "GraphicComponent",
toolbox: "ToolboxComponent",
tooltip: "TooltipComponent",
axisPointer: "AxisPointerComponent",
brush: "BrushComponent",
title: "TitleComponent",
timeline: "TimelineComponent",
markPoint: "MarkPointComponent",
markLine: "MarkLineComponent",
markArea: "MarkAreaComponent",
legend: "LegendComponent",
dataZoom: "DataZoomComponent",
visualMap: "VisualMapComponent",
aria: "AriaComponent",
dataset: "DatasetComponent",
// Dependencies
xAxis: "GridComponent",
yAxis: "GridComponent",
angleAxis: "PolarComponent",
radiusAxis: "PolarComponent"
};
const CHARTS_MAP = {
line: "LineChart",
bar: "BarChart",
pie: "PieChart",
scatter: "ScatterChart",
radar: "RadarChart",
map: "MapChart",
tree: "TreeChart",
treemap: "TreemapChart",
graph: "GraphChart",
gauge: "GaugeChart",
funnel: "FunnelChart",
parallel: "ParallelChart",
sankey: "SankeyChart",
boxplot: "BoxplotChart",
candlestick: "CandlestickChart",
effectScatter: "EffectScatterChart",
lines: "LinesChart",
heatmap: "HeatmapChart",
pictorialBar: "PictorialBarChart",
themeRiver: "ThemeRiverChart",
sunburst: "SunburstChart",
custom: "CustomChart"
};
const COMPONENTS_GL_MAP = {
grid3D: "Grid3DComponent",
geo3D: "Geo3DComponent",
globe: "GlobeComponent",
mapbox3D: "Mapbox3DComponent",
maptalks3D: "Maptalks3DComponent",
// Dependencies
xAxis3D: "Grid3DComponent",
yAxis3D: "Grid3DComponent",
zAxis3D: "Grid3DComponent"
};
const CHARTS_GL_MAP = {
bar3D: "Bar3DChart",
line3D: "Line3DChart",
scatter3D: "Scatter3DChart",
lines3D: "Lines3DChart",
polygons3D: "Polygons3DChart",
surface: "SurfaceChart",
map3D: "Map3DChart",
scatterGL: "ScatterGLChart",
graphGL: "GraphGLChart",
flowGL: "FlowGLChart",
linesGL: "LinesGLChart"
};
const FEATURES = ["UniversalTransition", "LabelLayout"];
const RENDERERS_MAP = {
canvas: "CanvasRenderer",
svg: "SVGRenderer"
};
const EXTENSIONS_MAP = {
bmap: "bmap/bmap"
// PENDING: There seem no examples that use dataTool
// dataTool: 'dataTool'
};
// Component that will be injected automatically in preprocessor
// These should be excluded util find they were used explicitly.
const MARKERS = ["markLine", "markArea", "markPoint"];
const INJECTED_COMPONENTS = [
...MARKERS,
"grid",
"axisPointer",
"aria" // TODO aria
];
// Component that was dependent.
const DEPENDENT_COMPONENTS = [
"xAxis",
"yAxis",
"angleAxis",
"radiusAxis",
"xAxis3D",
"yAxis3D",
"zAxis3D"
];
function createReverseMap(map) {
const reverseMap = {};
Object.keys(map).forEach(key => {
// Exclude dependencies.
if (DEPENDENT_COMPONENTS.includes(key)) {
return;
}
reverseMap[map[key]] = key;
});
return reverseMap;
}
const COMPONENTS_MAP_REVERSE = createReverseMap(COMPONENTS_MAP);
const CHARTS_MAP_REVERSE = createReverseMap(CHARTS_MAP);
const COMPONENTS_GL_MAP_REVERSE = createReverseMap(COMPONENTS_GL_MAP);
const CHARTS_GL_MAP_REVERSE = createReverseMap(CHARTS_GL_MAP);
function collectDeps(option) {
let deps = [];
if (option.options) {
// TODO getOption() doesn't have baseOption and options.
option.options.forEach(opt => {
deps = deps.concat(collectDeps(opt));
});
if (option.baseOption) {
deps = deps.concat(collectDeps(option.baseOption));
}
// Remove duplicates
return Array.from(new Set(deps));
}
Object.keys(option).forEach(key => {
if (INJECTED_COMPONENTS.includes(key)) {
return;
}
const val = option[key];
if (Array.isArray(val) && !val.length) {
return;
}
if (COMPONENTS_MAP[key]) {
deps.push(COMPONENTS_MAP[key]);
}
if (COMPONENTS_GL_MAP[key]) {
deps.push(COMPONENTS_GL_MAP[key]);
}
if (EXTENSIONS_MAP[key]) {
deps.push(key);
}
});
let series = option.series;
if (!Array.isArray(series)) {
series = [series];
}
series.forEach(seriesOpt => {
if (CHARTS_MAP[seriesOpt.type]) {
deps.push(CHARTS_MAP[seriesOpt.type]);
}
if (CHARTS_GL_MAP[seriesOpt.type]) {
deps.push(CHARTS_GL_MAP[seriesOpt.type]);
}
if (seriesOpt.type === "map") {
// Needs geo component when using map
deps.push(COMPONENTS_MAP.geo);
}
if (seriesOpt.coordinateSystem === "bmap") {
deps.push("bmap");
}
MARKERS.forEach(markerType => {
if (seriesOpt[markerType]) {
deps.push(COMPONENTS_MAP[markerType]);
}
});
// Features
if (seriesOpt.labelLayout) {
deps.push("LabelLayout");
}
if (seriesOpt.universalTransition) {
deps.push("UniversalTransition");
}
});
// Dataset transform
if (option.dataset && Array.isArray(option.dataset)) {
option.dataset.forEach(dataset => {
if (dataset.transform) {
deps.push("TransformComponent");
}
});
}
// Remove duplicates
return Array.from(new Set(deps));
}
function buildMinimalBundleCode(
deps,
{
includeType,
semi = false,
quote = "'",
multiline = false,
indent = " ",
maxLen = 80
}
) {
const options = {
semi,
quote,
multiline,
indent,
maxLen
};
const chartsImports = [];
const componentsImports = [];
const chartsGLImports = [];
const componentsGLImports = [];
const featuresImports = [];
const renderersImports = [];
const extensionImports = [];
deps.forEach(dep => {
if (dep.endsWith("Renderer")) {
renderersImports.push(dep);
} else if (CHARTS_MAP_REVERSE[dep]) {
chartsImports.push(dep);
if (includeType) {
chartsImports.push(dep.replace(/Chart$/, "SeriesOption"));
}
} else if (COMPONENTS_MAP_REVERSE[dep]) {
componentsImports.push(dep);
if (includeType) {
componentsImports.push(dep.replace(/Component$/, "ComponentOption"));
}
} else if (dep === "TransformComponent") {
// TransformComponent don't have individual option type.
// TODO will put in to an config if there are other similar components
componentsImports.push(dep);
} else if (CHARTS_GL_MAP_REVERSE[dep]) {
chartsGLImports.push(dep);
} else if (COMPONENTS_GL_MAP_REVERSE[dep]) {
componentsGLImports.push(dep);
} else if (FEATURES.includes(dep)) {
featuresImports.push(dep);
} else if (EXTENSIONS_MAP[dep]) {
extensionImports.push(dep);
}
});
const allImports = [
...componentsImports,
...chartsImports,
...componentsGLImports,
...chartsGLImports,
...renderersImports,
...featuresImports
];
const ECOptionTypeCode = typeItems(
allImports.filter(a => a.endsWith("Option")),
options
);
const importSources = [
[chartsImports, "echarts/charts"],
[componentsImports, "echarts/components"],
[featuresImports, "echarts/features"],
[renderersImports, "echarts/renderers"],
[chartsGLImports, "echarts-gl/charts"],
[componentsGLImports, "echarts-gl/components"]
].filter(a => a[0].length > 0);
const importStatements = importSources.map(([imports, mod]) =>
importItems(
imports.filter(a => !a.endsWith("Option")),
mod,
options
)
);
const semiStr = semi ? ";" : "";
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}`
);
const importTypeStatements = importSources
.map(([imports, mod]) =>
importItems(
imports.filter(a => a.endsWith("Option")),
mod,
{ ...options, type: true }
)
)
.filter(Boolean);
importStatements.push(...importTypeStatements);
}
return `import { use } from ${quote}echarts/core${quote}${semiStr}
${importStatements.join("\n")}
${useItems(
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]}`);
}
/** import */
function importItems(items, module, options) {
if (items.length === 0) {
return "";
}
const { multiline, maxLen } = options;
if (multiline) {
return importMultiLine(items, module, options);
}
const singleLine = importSingleLine(items, module, options);
if (singleLine.length <= maxLen) {
return singleLine;
}
return importMultiLine(items, module, options);
}
// import { foo, bar } from 'module'
function importSingleLine(items, module, { type, semi, quote }) {
const typeStr = type ? "type " : "";
const semiStr = semi ? ";" : "";
return `import ${typeStr}{ ${items.join(
", "
)} } from ${quote}${module}${quote}${semiStr}`;
}
// import {
// foo,
// bar
// } from 'module'
function importMultiLine(items, module, { type, indent, semi, quote }) {
const typeStr = type ? "type " : "";
const semiStr = semi ? ";" : "";
return `import ${typeStr}{
${items.map(item => `${indent}${item}`).join(",\n")}
} from ${quote}${module}${quote}${semiStr}`;
}
/** use */
function useItems(items, options) {
if (items.length === 0) {
return "";
}
const { multiline, maxLen } = options;
if (multiline) {
return useMultiLine(items, options);
}
const singleLine = useSingleLine(items, options);
if (singleLine.length <= maxLen) {
return singleLine;
}
return useMultiLine(items, options);
}
// use([foo, bar])
function useSingleLine(items, { semi }) {
const semiStr = semi ? ";" : "";
return `use([${items.join(`, `)}])${semiStr}`;
}
// use([
// foo,
// bar
// ])
function useMultiLine(items, { indent, semi }) {
const semiStr = semi ? ";" : "";
return `use([
${items.map(item => `${indent}${item}`).join(`,\n`)}
])${semiStr}`;
}
/** type */
function typeItems(items, options) {
const { multiline, maxLen } = options;
if (items.length === 0) {
return "";
}
if (multiline) {
return typeMultiLine(items, options);
}
const singleLine = typeSingleLine(items, options);
if (singleLine.length <= maxLen) {
return singleLine;
}
return typeMultiLine(items, options);
}
// type EChartsOption = ComposeOption<FooOption | BarOption>
function typeSingleLine(items, { semi }) {
const semiStr = semi ? ";" : "";
return `type EChartsOption = ComposeOption<${items.join(` | `)}>${semiStr}`;
}
// type EChartsOption = ComposeOption<
// | FooOption
// | BarOption
// >
function typeMultiLine(items, { indent, semi }) {
const semiStr = semi ? ";" : "";
return `type EChartsOption = ComposeOption<
${items.map(item => `${indent}| ${item}`).join("\n")}
>${semiStr}`;
}
export function getImportsFromOption(
option,
{ renderer = "canvas", ...options }
) {
return buildMinimalBundleCode(
[...collectDeps(option), RENDERERS_MAP[renderer]],
options
);
}

View File

@ -1,30 +1,44 @@
/* eslint-disable @typescript-eslint/ban-types */
import type { Ref, DefineComponent } from "vue-demi";
import type { Ref, DefineComponent, InjectionKey } from "vue-demi";
import type {
Option,
Theme,
InitOptions,
UpdateOptions,
LoadingOptions,
EChartsType,
Emits
Emits,
ThemeInjection,
InitOptionsInjection,
UpdateOptionsInjection,
LoadingOptionsInjection
} 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 const THEME_KEY: InjectionKey<ThemeInjection>;
declare const INIT_OPTIONS_KEY: InjectionKey<InitOptionsInjection>;
declare const UPDATE_OPTIONS_KEY: InjectionKey<UpdateOptionsInjection>;
declare const LOADING_OPTIONS_KEY: InjectionKey<LoadingOptionsInjection>;
declare type ChartProps = {
loading?: boolean;
loadingOptions?: Record<string, unknown>;
autoresize?: boolean;
option?: Option;
theme?: string | Record<string, unknown>;
theme?: Theme;
initOptions?: InitOptions;
updateOptions?: UpdateOptions;
loadingOptions?: LoadingOptions;
option?: Option;
autoresize?: boolean;
loading?: boolean;
group?: string;
manualUpdate?: boolean;
};
// convert Emits to Props
// click => onClick
declare type ChartEventProps = {
[key in keyof Emits as key extends string
? `on${Capitalize<key>}`
: never]?: Emits[key];
};
type MethodNames =
| "getWidth"
| "getHeight"
@ -46,18 +60,15 @@ type MethodNames =
declare type ChartMethods = Pick<EChartsType, MethodNames>;
declare const Chart: DefineComponent<
ChartProps,
ChartProps & ChartEventProps,
{
root: Ref<HTMLElement | undefined>;
chart: Ref<EChartsType | undefined>;
},
{},
{},
ChartMethods,
{},
{},
Emits
ChartMethods
>;
export default Chart;
export { INIT_OPTIONS_KEY, LOADING_OPTIONS_KEY, THEME_KEY, UPDATE_OPTIONS_KEY };
export { THEME_KEY, INIT_OPTIONS_KEY, UPDATE_OPTIONS_KEY, LOADING_OPTIONS_KEY };

View File

@ -1,5 +1,7 @@
import { init, type SetOptionOpts } from "echarts/core";
import type { Ref } from "vue";
import { init } from "echarts/core";
import type { SetOptionOpts, ECElementEvent, ElementEvent } from "echarts/core";
import type { Ref } from "vue-demi";
export type Injection<T> = T | null | Ref<T | null> | { value: T | null };
@ -17,20 +19,58 @@ export type UpdateOptionsInjection = Injection<UpdateOptions>;
export type EChartsType = ReturnType<InitType>;
type ZRenderType = ReturnType<EChartsType["getZr"]>;
export type EventTarget = EChartsType | ZRenderType;
type SetOptionType = EChartsType["setOption"];
export type Option = Parameters<SetOptionType>[0];
type EChartsMouseEventName =
export type AutoResize =
| boolean
| {
throttle?: number;
onResize?: () => void;
};
export type LoadingOptions = {
text?: string;
textColor?: string;
fontSize?: number | string;
fontWeight?: number | string;
fontStyle?: string;
fontFamily?: string;
maskColor?: string;
showSpinner?: boolean;
color?: string;
spinnerRadius?: number;
lineWidth?: number;
zlevel?: number;
};
export type LoadingOptionsInjection = Injection<LoadingOptions>;
type MouseEventName =
| "click"
| "dblclick"
| "mouseout"
| "mouseover"
| "mouseup"
| "mousedown"
| "mousemove"
| "mouseup"
| "mouseover"
| "mouseout"
| "globalout"
| "contextmenu";
type EChartsOtherEventName =
| "contextmenu"
| "globalout";
type ElementEventName =
| MouseEventName
| "mousewheel"
| "drag"
| "dragstart"
| "dragend"
| "dragenter"
| "dragleave"
| "dragover"
| "drop";
type ZRenderEventName = `zr:${ElementEventName}`;
type OtherEventName =
| "highlight"
| "downplay"
| "selectchanged"
@ -57,46 +97,22 @@ type EChartsOtherEventName =
| "brush"
| "brushEnd"
| "brushselected"
| "globalcursortaken"
| "rendered"
| "finished";
type ZRenderEventName =
| "click"
| "dblclick"
| "mousewheel"
| "mouseout"
| "mouseover"
| "mouseup"
| "mousedown"
| "mousemove"
| "contextmenu"
| "drag"
| "dragstart"
| "dragend"
| "dragenter"
| "dragleave"
| "dragover"
| "drop"
| "globalout";
type OtherEventName = EChartsOtherEventName | `zr:${ZRenderEventName}`;
// See https://echarts.apache.org/en/api.html#events.Mouse%20events
interface MouseEventParams {
componentType: string;
seriesType: string;
seriesIndex: number;
seriesName: string;
name: string;
dataIndex: number;
color: string;
}
| "globalcursortaken";
type MouseEmits = {
[k in EChartsMouseEventName]: (params: MouseEventParams) => boolean;
[key in MouseEventName]: (params: ECElementEvent) => void;
};
type ZRenderEmits = {
[key in ZRenderEventName]: (params: ElementEvent) => void;
};
type OtherEmits = {
[key in OtherEventName]: null;
[key in OtherEventName]: (params: any) => void;
};
export type Emits = MouseEmits & OtherEmits;
export type Emits = MouseEmits &
OtherEmits & {
rendered: (params: { elapsedTime: number }) => void;
finished: () => void;
} & ZRenderEmits;

View File

@ -1,4 +1,5 @@
import { unref } from "vue-demi";
import { unref, isRef } from "vue-demi";
import type { Injection } from "./types";
type Attrs = {
@ -26,7 +27,7 @@ export function unwrapInjected<T, V>(
injection: Injection<T>,
defaultValue: V
): T | V {
const value = unref(injection);
const value = isRef(injection) ? unref(injection) : injection;
if (value && typeof value === "object" && "value" in value) {
return value.value || defaultValue;

View File

@ -26,21 +26,9 @@ export function register(): boolean {
// if the browser doesn't support native classes.
const reg = new Function(
"tag",
`class EChartsElement extends HTMLElement {
__dispose = null;
disconnectedCallback() {
if (this.__dispose) {
this.__dispose();
this.__dispose = null;
}
}
}
if (customElements.get(tag) == null) {
customElements.define(tag, EChartsElement);
}
`
// Use esbuild repl to keep build process simple
// https://esbuild.github.io/try/#dAAwLjIzLjAALS1taW5pZnkAY2xhc3MgRUNoYXJ0c0VsZW1lbnQgZXh0ZW5kcyBIVE1MRWxlbWVudCB7CiAgX19kaXNwb3NlID0gbnVsbDsKCiAgZGlzY29ubmVjdGVkQ2FsbGJhY2soKSB7CiAgICBpZiAodGhpcy5fX2Rpc3Bvc2UpIHsKICAgICAgdGhpcy5fX2Rpc3Bvc2UoKTsKICAgICAgdGhpcy5fX2Rpc3Bvc2UgPSBudWxsOwogICAgfQogIH0KfQoKaWYgKGN1c3RvbUVsZW1lbnRzLmdldCh0YWcpID09IG51bGwpIHsKICBjdXN0b21FbGVtZW50cy5kZWZpbmUodGFnLCBFQ2hhcnRzRWxlbWVudCk7Cn0K
"class EChartsElement extends HTMLElement{__dispose=null;disconnectedCallback(){this.__dispose&&(this.__dispose(),this.__dispose=null)}}customElements.get(tag)==null&&customElements.define(tag,EChartsElement);"
);
reg(TAG_NAME);
} catch (e) {

View File

@ -1,7 +1,6 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const nested = require("postcss-nested");
import nested from "postcss-nested";
module.exports = {
export default {
outputDir: "demo",
css: {
loaderOptions: {
@ -19,7 +18,24 @@ module.exports = {
.rule("svg")
.clear()
.test(/\.svg$/)
.use("raw-loader")
.loader("raw-loader");
.type("asset/source");
config.module
.rule("wasm")
.test(/\.wasm$/)
.type("asset/resource")
.set("generator", {
filename: "[name].[hash:8][ext]"
});
config.plugin("define").tap(([options]) => [
{
...options,
__CSP__: "false"
}
]);
},
devServer: {
allowedHosts: "all"
}
};