Compare commits

...

51 Commits

Author SHA1 Message Date
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
48 changed files with 5918 additions and 4366 deletions

View File

@ -10,7 +10,8 @@ module.exports = {
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"vue/multi-word-component-names": "off"
},
overrides: [
{

View File

@ -1,3 +1,51 @@
## 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).

View File

@ -1,8 +1,15 @@
<h1 align="center">Vue-ECharts</h1>
<p align="center">Vue.js <sup>(v2/v3)</sup> component for Apache ECharts <sup>(v5)</sup>.</p>
<p align="center">Vue.js <sup>(v2/v3)</sup> component for Apache ECharts <sup>(v5)</sup>.</p>
<p align="center"><a href="https://vue-echarts.dev/">View Demo →</a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"/></a><a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"> <img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"/></a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"/></a> <a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"><img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"/></a></p>
> [!IMPORTANT]
> We have released an [import code generator](https://vue-echarts.dev/#codegen) that can generate precise import code by pasting the `option` code.
>
> ![](https://github.com/ecomfe/vue-echarts/assets/1726061/f9c38a06-3422-4f0e-ab8c-f242d9aea9aa)
>
> [Try it →](https://vue-echarts.dev/#codegen)
---
@ -16,20 +23,21 @@ Not ready yet? Read documentation for older versions [here →](https://github.c
### npm & ESM
```bash
$ npm install echarts vue-echarts
```sh
npm i echarts vue-echarts
```
To make `vue-echarts` work for _Vue 2_ (<2.7.0), you need to have `@vue/composition-api` installed:
To make `vue-echarts` work for _Vue 2_ (<2.7.0), you need to have `@vue/composition-api` installed (`@vue/runtime-core` for TypeScript support):
```sh
npm i -D @vue/composition-api
npm i @vue/composition-api
npm i @vue/runtime-core # for TypeScript support
```
If you are using _NuxtJS_ on top of _Vue 2_, you'll also need `@nuxtjs/composition-api`:
If you are using _NuxtJS_ on top of _Vue 2_, you'll need `@nuxtjs/composition-api`:
```sh
npm i -D @nuxtjs/composition-api
npm i @nuxtjs/composition-api
```
And then add `'@nuxtjs/composition-api/module'` in the `buildModules` option in your `nuxt.config.js`.
@ -208,7 +216,10 @@ 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.
>
> [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:
@ -225,9 +236,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.45"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.5.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.19"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.9"></script>
```
<!-- vue3Scripts:end -->
@ -245,9 +256,9 @@ app.component('v-chart', VueECharts)
<!-- vue2Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.5.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.9"></script>
```
<!-- vue2Scripts:end -->
@ -290,9 +301,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`)
@ -465,7 +476,7 @@ 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.
- 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 (and also `@vue/runtime-core` for TypeScript support).
### Props
@ -490,9 +501,9 @@ The following breaking changes are introduced in `vue-echarts@6`:
## Local development
```bash
$ npm i
$ npm run serve
```sh
pnpm i
pnpm serve
```
Open `http://localhost:8080` to see the demo.

View File

@ -1,8 +1,15 @@
<h1 align="center">Vue-ECharts</h1>
<p align="center">Apache ECharts <sup>(v5)</sup> 的 Vue.js <sup>(v2/v3)</sup> 组件。</p>
<p align="center">Apache ECharts <sup>(v5)</sup> 的 Vue.js <sup>(v2/v3)</sup> 组件。</p>
<p align="center"><a href="https://vue-echarts.dev/">查看 Demo →</a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"/></a><a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"> <img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"/></a></p>
<p align="center"><a href="https:///pr.new/ecomfe/vue-echarts"><img alt="Open in Codeflow" src="https://developer.stackblitz.com/img/open_in_codeflow.svg" height="28"/></a> <a href="https://codesandbox.io/p/github/ecomfe/vue-echarts"><img alt="Edit in CodeSandbox" src="https://assets.codesandbox.io/github/button-edit-lime.svg" height="28"/></a></p>
> [!IMPORTANT]
> 我们新发布了一个[导入代码生成器](https://vue-echarts.dev/#codegen),只需要把`option` 代码粘贴进去,就可以得到精确的导入代码。
>
> ![](https://github.com/ecomfe/vue-echarts/assets/1726061/f9c38a06-3422-4f0e-ab8c-f242d9aea9aa)
>
> [试一试 →](https://vue-echarts.dev/#codegen)
---
@ -16,20 +23,21 @@
### npm & ESM
```bash
$ npm install echarts vue-echarts
```sh
npm i echarts vue-echarts
```
要在 _Vue 2_<2.7.0下使用 `vue-echarts`需要确保 `@vue/composition-api` 已经安装
要在 _Vue 2_<2.7.0下使用 `vue-echarts`需要确保 `@vue/composition-api` 已经安装TypeScript 支持还需要 `@vue/runtime-core`
```sh
npm i -D @vue/composition-api
npm i @vue/composition-api
npm i @vue/runtime-core # TypeScript 支持
```
如果你在使用基于 _Vue 2_ _NuxtJS_那么还需要安装 `@nuxtjs/composition-api`
如果你在使用基于 _Vue 2_ _NuxtJS_需要安装 `@nuxtjs/composition-api`
```sh
npm i -D @nuxtjs/composition-api
npm i @nuxtjs/composition-api
```
然后在 `nuxt.config.js` `buildModules` 选项中添加 `'@nuxtjs/composition-api/module'`
@ -208,7 +216,10 @@ export default {
</details>
为了更小的打包体积,我们建议手动从 ECharts 引入单个图表和组件。请参考所有支持的渲染器/图表/组件。[前往 →](https://github.com/apache/echarts/blob/master/src/echarts.all.ts)
> [!IMPORTANT]
> 我们鼓励手动从 ECharts 中引入组件和图表,以减小打包体积。我们已经为此构建了一个[导入代码生成器](https://vue-echarts.dev/#codegen)。你只需要把`option` 代码粘贴进去,就可以得到精确的导入代码。
>
> [试一试 →](https://vue-echarts.dev/#codegen)
但如果你实在需要全量引入 ECharts 从而无需手动引入模块,只需要在代码中添加:
@ -225,9 +236,9 @@ import "echarts";
<!-- vue3Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.45"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.5.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3.4.19"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.9"></script>
```
<!-- vue3Scripts:end -->
@ -245,9 +256,9 @@ app.component('v-chart', VueECharts)
<!-- vue2Scripts:start -->
```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.5.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-echarts@6.6.9"></script>
```
<!-- vue2Scripts:end -->
@ -290,9 +301,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`
@ -465,7 +476,7 @@ import { THEME_KEY } from 'vue-echarts'
### Vue 2 支持
- 要在 `vue@2.7.0` 之前的版本中使用 Vue-ECharts必须安装 `@vue/composition-api`
- 要在 `vue@2.7.0` 之前的版本中使用 Vue-ECharts必须安装 `@vue/composition-api`(还需要安装 `@vue/runtime-core` 来支持 TypeScript
### Prop
@ -490,9 +501,9 @@ import { THEME_KEY } from 'vue-echarts'
## 本地开发
```bash
$ npm i
$ npm run serve
```sh
pnpm i
pnpm serve
```
打开 `http://localhost:8080` 来查看 demo。

View File

@ -1,7 +1,7 @@
{
"name": "vue-echarts",
"version": "6.5.3",
"description": "Vue.js component for Apache ECharts.",
"version": "6.6.9",
"description": "Vue.js component for Apache ECharts.",
"author": "GU Yiling <justice360@gmail.com>",
"scripts": {
"serve": "vue-cli-service serve",
@ -23,49 +23,57 @@
],
"dependencies": {
"resize-detector": "^0.3.0",
"vue-demi": "^0.13.2"
"vue-demi": "^0.13.11"
},
"devDependencies": {
"@babel/core": "^7.18.6",
"@rollup/plugin-node-resolve": "^11.2.1",
"@babel/core": "^7.23.2",
"@highlightjs/vue-plugin": "^2.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vue/cli-plugin-babel": "^5.0.7",
"@vue/cli-plugin-eslint": "^5.0.7",
"@vue/cli-plugin-typescript": "^5.0.7",
"@vue/cli-service": "^5.0.7",
"@vue/compiler-sfc": "^3.2.37",
"@vue/composition-api": "^1.7.0",
"@vercel/analytics": "^1.1.1",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/compiler-sfc": "^3.3.7",
"@vue/composition-api": "^1.7.2",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vueuse/core": "^10.5.0",
"comment-mark": "^1.1.1",
"core-js": "^3.23.3",
"echarts": "^5.4.1",
"core-js": "^3.33.2",
"echarts": "^5.4.3",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0",
"esbuild-wasm": "^0.19.2",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^8.7.1",
"postcss": "^8.4.14",
"highlight.js": "^11.9.0",
"pinia": "^2.1.7",
"postcss": "^8.4.31",
"postcss-loader": "^5.3.0",
"postcss-nested": "^5.0.6",
"prettier": "^2.7.1",
"qs": "^6.11.0",
"prettier": "^2.8.8",
"raw-loader": "^4.0.2",
"resize-detector": "^0.3.0",
"rimraf": "^3.0.2",
"rollup": "^2.75.7",
"rollup-plugin-dts": "^4.2.2",
"rollup": "^2.79.1",
"rollup-plugin-dts": "^4.2.3",
"rollup-plugin-styles": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-ts": "^2.0.7",
"tslib": "^2.4.0",
"tslib": "^2.6.2",
"typescript": "4.6.4",
"vue": "^3.2.37",
"vue2": "npm:vue@^2.7.14",
"webpack": "^5.73.0"
"vue": "^3.3.7",
"vue2": "npm:vue@^2.7.15",
"webpack": "^5.89.0"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.5",
"@vue/runtime-core": "^3.0.0",
"echarts": "^5.4.1",
"vue": "^2.6.12 || ^3.1.1"
},
@ -74,6 +82,9 @@
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
},
"@vue/runtime-core": {
"optional": true
}
},
"repository": "https://github.com/ecomfe/vue-echarts.git",

6454
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,25 +1,28 @@
import typescript from "rollup-plugin-ts";
import { terser } from "rollup-plugin-terser";
import terser from "@rollup/plugin-terser";
import resolve from "@rollup/plugin-node-resolve";
import replace from "@rollup/plugin-replace";
import styles from "rollup-plugin-styles";
import { injectVueDemi } from "./scripts/rollup";
/**
* Convert Rollup option to a style extracted/injected version
* @param {import('rollup').RollupOptions} option
* @param {boolean} extract
* @returns {import('rollup').RollupOptions}
* 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 handleStyle(option, extract) {
// inject styles plugin
const result = { ...option };
function configBuild(options, csp) {
const result = { ...options };
const { plugins, output } = result;
result.plugins = (plugins || []).concat(
extract ? styles({ mode: ["extract", "style.css"] }) : styles()
);
result.plugins = [
...(csp ? [replace({ __CSP__: `${csp}`, preventAssignment: true })] : []),
...plugins,
csp ? styles({ mode: ["extract", "style.css"] }) : styles()
];
// modify output file names
if (extract && output) {
if (csp && output) {
result.output = (Array.isArray(output) ? output : [output]).map(output => {
return {
...output,
@ -33,7 +36,7 @@ function handleStyle(option, extract) {
}
/** @type {import('rollup').RollupOptions[]} */
const options = [
const builds = [
{
input: "src/index.ts",
plugins: [
@ -133,6 +136,6 @@ const options = [
];
export default [
...options.map(option => handleStyle(option, false)),
...options.map(option => handleStyle(option, true))
...builds.map(options => configBuild(options, false)),
...builds.map(options => configBuild(options, true))
];

View File

@ -9,6 +9,14 @@ const options = [
file: "dist/index.vue2.d.ts",
format: "esm"
}
},
{
input: "src/index.vue2_7.d.ts",
plugins: [dts()],
output: {
file: "dist/index.vue2_7.d.ts",
format: "esm"
}
}
];

View File

@ -1,16 +1,18 @@
const fs = require("fs");
const { readFileSync, writeFileSync } = require("fs");
const { resolve } = require("path");
const commentMark = require("comment-mark");
const { name, version } = require("../package.json");
const { readFile, writeFile } = fs.promises;
function resolvePath(...parts) {
return resolve(__dirname, ...parts);
}
const CDN_PREFIX = "https://cdn.jsdelivr.net/npm/";
const DEP_VERSIONS = {
"vue@3": "3.2.45",
"vue@2": "2.7.14",
echarts: "5.4.1",
"vue@3": "3.4.19",
"vue@2": "2.7.16",
echarts: "5.4.3",
[name]: version
};
@ -39,29 +41,20 @@ const scripts = {
};
const README_FILES = ["README.md", "README.zh-Hans.md"].map(name =>
resolve(__dirname, "..", name)
resolvePath("..", 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

@ -6,6 +6,7 @@ const packageFile = path.resolve(__dirname, "../package.json");
const typesPaths = {
3: "dist/index.d.ts",
2.7: "dist/index.vue2_7.d.ts",
2: "dist/index.vue2.d.ts"
};
@ -36,6 +37,8 @@ if (!Vue || typeof Vue.version !== "string") {
);
} else if (Vue.version.startsWith("3.")) {
switchVersion(3);
} else if (Vue.version.startsWith("2.7.")) {
switchVersion(2.7);
} else if (Vue.version.startsWith("2.")) {
switchVersion(2);
} else {

View File

@ -41,7 +41,8 @@ import { omitOn, unwrapInjected } from "./utils";
import { register, TAG_NAME, 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);
@ -72,6 +73,7 @@ export default defineComponent({
inheritAttrs: false,
setup(props, { attrs }) {
const root = shallowRef<EChartsElement>();
const inner = shallowRef<HTMLElement>();
const chart = shallowRef<EChartsType>();
const manualOption = shallowRef<Option>();
const defaultTheme = inject(THEME_KEY, null);
@ -98,12 +100,12 @@ export default defineComponent({
const listeners = getCurrentInstance().proxy.$listeners;
function init(option?: Option) {
if (!root.value) {
if (!inner.value) {
return;
}
const instance = (chart.value = initChart(
root.value,
inner.value,
realTheme.value,
realInitOptions.value
));
@ -232,7 +234,6 @@ export default defineComponent({
if (!chart.value) {
init();
} else {
console.log(`notMerge: ${option !== oldOption}`);
chart.value.setOption(option, {
// mutating `option` will lead to `notMerge: false` and
// replacing it with new reference will lead to `notMerge: true`
@ -271,7 +272,7 @@ export default defineComponent({
useLoading(chart, loading, loadingOptions);
useAutoresize(chart, autoresize, root);
useAutoresize(chart, autoresize, inner);
onMounted(() => {
init();
@ -292,6 +293,7 @@ export default defineComponent({
return {
chart,
root,
inner,
setOption,
nonEventAttrs,
...publicApi
@ -305,6 +307,8 @@ export default defineComponent({
) as any;
attrs.ref = "root";
attrs.class = attrs.class ? ["echarts"].concat(attrs.class) : "echarts";
return h(TAG_NAME, attrs);
return h(TAG_NAME, attrs, [
h("div", { ref: "inner", class: "vue-echarts-inner" })
]);
}
});

View File

@ -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,26 +1,42 @@
import { Ref, watch } from "vue-demi";
import { watch, type Ref, type PropType } from "vue-demi";
import { throttle } from "echarts/core";
import { addListener, removeListener, ResizeCallback } from "resize-detector";
import { EChartsType } from "../types";
import {
addListener,
removeListener,
type ResizeCallback
} from "resize-detector";
import { type EChartsType } from "../types";
type AutoresizeProp =
| boolean
| {
throttle?: number;
onResize?: () => void;
};
export function useAutoresize(
chart: Ref<EChartsType | undefined>,
autoresize: Ref<boolean>,
autoresize: Ref<AutoresizeProp | undefined>,
root: Ref<HTMLElement | undefined>
): void {
let resizeListener: ResizeCallback | null = null;
watch([root, chart, autoresize], ([root, chart, autoresize], _, cleanup) => {
if (root && chart && autoresize) {
resizeListener = throttle(() => {
chart.resize();
}, 100);
const autoresizeOptions = autoresize === true ? {} : autoresize;
const { throttle: wait = 100, onResize } = autoresizeOptions;
const callback = () => {
chart.resize();
onResize?.();
};
resizeListener = wait ? throttle(callback, wait) : callback;
addListener(root, resizeListener);
}
cleanup(() => {
if (resizeListener && root) {
if (root && resizeListener) {
removeListener(root, resizeListener);
}
});
@ -28,5 +44,5 @@ export function useAutoresize(
}
export const autoresizeProps = {
autoresize: Boolean
autoresize: [Boolean, Object] as PropType<AutoresizeProp>
};

View File

@ -4,21 +4,20 @@ import {
computed,
watchEffect,
type Ref,
type InjectionKey
type InjectionKey,
type PropType
} from "vue-demi";
import { EChartsType } from "../types";
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 +41,5 @@ export function useLoading(
export const loadingProps = {
loading: Boolean,
loadingOptions: Object
loadingOptions: Object as PropType<LoadingOptions>
};

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

@ -0,0 +1,424 @@
<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 { 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: "https://cdn.jsdelivr.net/npm/esbuild-wasm@0.19.2/esbuild.wasm"
});
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,7 +9,11 @@ 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,
data: ["scatter"]
@ -57,7 +61,11 @@ export const c1 = {
]
};
export const c2 = {
const c2 = {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
legend: {
top: 20,
data: ["scatter"]
@ -103,3 +111,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,95 @@ 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",
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",
y: "bottom",
x: "right",
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"
}
},
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"
}
},
zlevel: 1
}
]
};
{
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,43 @@
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",
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

@ -6,40 +6,43 @@ 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"
},
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
};
}

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,90 @@
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);
});
const scoreRadar = computed(() => {
return {
title: {
text: "Player Ability"
},
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
radar: {
indicator: scores.value.map(({ name, max }) => {
return { name, max };
})
},
series: [
{
name: "Value",
type: "radar",
data: [{ value: scores.value.map(({ value }) => value) }]
}
]
};
});
function getRadarData(activeIndex: number) {
return {
animation: false,
title: {
text: "Player Ability"
},
tooltip: {},
radar: {
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,
scoreRadar,
getRadarData,
increase,
isMax,
isMin
};
});

View File

@ -45,96 +45,99 @@ 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 {
textStyle: {
fontFamily: 'Inter, "Helvetica Neue", Arial, sans-serif',
fontWeight: 300
},
title: {
text: "Life Expectancy vs. GDP by country"
},
legend: {
right: 10,
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,56 @@
<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">
<label>
<input type="checkbox" v-model="connected" />
Connected
</label>
</p>
</template>
</v-example>
</template>

View File

@ -0,0 +1,100 @@
<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;
padding: 1.5em 2em;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
box-shadow: 0 0 45px rgba(0, 0, 0, 0.2);
}
}
.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;
padding: 1em 0;
border: none;
border-radius: 0;
box-shadow: none;
}
}
}
@media (min-width: 980px) {
.fig.half {
.echarts {
width: 28vw;
min-width: 240px;
padding: 1em 1.5em;
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,109 @@
<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: {
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,113 @@
<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",
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",
left: 0,
right: 0,
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,45 @@
<script setup>
import { use } from "echarts/core";
import { LineChart } from "echarts/charts";
import {
PolarComponent,
TitleComponent,
LegendComponent,
TooltipComponent
} from "echarts/components";
import { 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");
</script>
<template>
<v-example id="polar" title="Polar plot" desc="(with built-in theme)">
<v-chart
:option="option"
autoresize
:theme="theme"
:style="theme === 'dark' ? 'background-color: #100c2a' : ''"
/>
<template #extra>
<p class="actions">
Theme
<select v-model="theme">
<option :value="null">Default</option>
<option value="dark">Dark</option>
</select>
</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,11 @@
import { inject } from "@vercel/analytics";
import { createApp } from "vue";
import { createPinia } from "pinia";
import Demo from "./Demo.vue";
createApp(Demo).mount("#app");
inject();
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
);
}

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

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/ban-types */
import type { Ref, DefineComponent } from "vue-demi";
import type { DefineComponent } from "@vue/runtime-core";
import type { Ref } from "vue-demi";
import type {
Option,
InitOptions,

63
src/index.vue2_7.d.ts vendored Normal file
View File

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

View File

@ -1 +1,2 @@
x-vue-echarts{display:block;width:100%;height:100%;min-width:0}
x-vue-echarts{display:flex;flex-direction:column;width:100%;height:100%;min-width:0}
.vue-echarts-inner{flex-grow:1;min-width:0}

View File

@ -1,9 +1,5 @@
import {
init,
type SetOptionOpts,
type ECElementEvent,
type ElementEvent
} from "echarts/core";
import { init } from "echarts/core";
import type { SetOptionOpts, ECElementEvent, ElementEvent } from "echarts";
import type { Ref } from "vue";
export type Injection<T> = T | null | Ref<T | null> | { value: T | null };
@ -22,9 +18,25 @@ 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];
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;
};
type MouseEventName =
| "click"
| "dblclick"

View File

@ -19,8 +19,14 @@ module.exports = {
.rule("svg")
.clear()
.test(/\.svg$/)
.use("raw-loader")
.loader("raw-loader");
.type("asset/source");
config.plugin("define").tap(([options]) => [
{
...options,
__CSP__: "false"
}
]);
},
devServer: {
allowedHosts: "all"