You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2021/09/10 02:49:54 UTC

[echarts-examples] 02/15: use prettier to format code

This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/echarts-examples.git

commit 16a668348fea384ceb3c90558616e04eda103199
Author: pissang <bm...@gmail.com>
AuthorDate: Mon Sep 6 12:59:21 2021 +0800

    use prettier to format code
---
 .babelrc                         |   11 +-
 .eslintrc.js                     |   13 +
 .eslintrc.yaml                   |  194 ------
 .gitignore                       |    1 +
 .gitignore => .prettierignore    |   14 +-
 .prettierrc                      |    7 +
 .vscode/settings.json            |    4 +
 README.md                        |   24 +-
 build/copyResource.js            |  139 ++--
 build/server.js                  |   24 +-
 build/webpack.config.js          |  155 ++---
 common/buildCode.js              |  611 +++++++++---------
 common/compareImage.js           |   81 +--
 common/task.js                   |   96 ++-
 config/common.js                 |    6 +-
 config/env.asf.js                |    2 +-
 config/env.dev.js                |    2 +-
 config/env.localsite.js          |    2 +-
 e2e/cases/README.md              |    2 +-
 e2e/cases/liquidfill.js          |   14 +-
 e2e/cases/wordcloud.js           |  230 +++----
 e2e/config.js                    |   58 +-
 e2e/main.js                      | 1290 +++++++++++++++++++++-----------------
 e2e/package.json                 |    3 +-
 e2e/preview.html                 |   47 +-
 e2e/report.html                  |  353 ++++++-----
 e2e/template.html                |   47 +-
 e2e/tsconfig.json                |   18 +-
 e2e/webpack.config.js            |   29 +-
 public/en/editor.html            |   53 +-
 public/en/index.html             |   54 +-
 public/en/view.html              |   53 +-
 public/zh/editor.html            |   53 +-
 public/zh/index.html             |   54 +-
 public/zh/view.html              |   53 +-
 src/common/config.js             |  224 +++----
 src/common/helper.js             |  105 ++--
 src/common/i18n.js               |  236 +++----
 src/common/store.js              |   80 +--
 src/dep/showDebugDirtyRect.js    |  152 ++---
 src/editor/CodeAce.vue           |  195 +++---
 src/editor/CodeMonaco.vue        |  267 ++++----
 src/editor/Editor.vue            |  772 ++++++++++++-----------
 src/editor/FullCodePreview.vue   |  139 ++--
 src/editor/Preview.vue           |  728 ++++++++++-----------
 src/editor/View.vue              |   27 +-
 src/editor/downloadExample.js    |   22 +-
 src/editor/object-visualizer.css |  122 ++--
 src/editor/sandbox.js            |  376 +++++------
 src/editor/transformTs.js        |   10 +-
 src/explore/ExampleCard.vue      |  236 +++----
 src/explore/Explore.vue          |  672 ++++++++++----------
 src/main.js                      |   61 +-
 src/style/color.scss             |   42 +-
 src/style/config.xl.scss         | 1219 ++++++++++++++++++-----------------
 tool/build-example.js            |  695 ++++++++++----------
 tool/screenshot.html             |  317 ++++++----
 tool/seedrandom.js               |  459 ++++++++------
 tool/updateFrontMatter.js        |   43 +-
 59 files changed, 5707 insertions(+), 5289 deletions(-)

diff --git a/.babelrc b/.babelrc
index e87d71a..9da67db 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,10 @@
 {
-    "presets": [["@babel/preset-env", {
+  "presets": [
+    [
+      "@babel/preset-env",
+      {
         "modules": false
-    }]]
-}
\ No newline at end of file
+      }
+    ]
+  ]
+}
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..d64a787
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,13 @@
+module.exports = {
+  root: true,
+  env: {
+    browser: true,
+    node: true
+  },
+  extends: ['prettier', 'prettier/vue'],
+  plugins: ['prettier'],
+  // add your custom rules here
+  rules: {
+    'space-before-function-paren': 0
+  }
+};
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
deleted file mode 100644
index e4a2cee..0000000
--- a/.eslintrc.yaml
+++ /dev/null
@@ -1,194 +0,0 @@
-# Note:
-# If eslint does not work in VSCode, please check:
-# (1) Whether "@typescript-eslint/eslint-plugin" and "@typescript-eslint/parser"
-# are npm installed locally. Should better in the same version.
-# (2) Whether "VSCode ESlint extension" is installed.
-# (3) If the project folder is not the root folder of your working space, please
-# config the "VSCode ESlint extension" in "settings":
-# ```json
-# "eslint.workingDirectories": [{"mode": "auto"}]
-# ```
-# Note that it should be "workingDirectories" rather than "WorkingDirectories".
-
-rules:
-    # Check the rules in: node_modules/@typescript-eslint/eslint-plugin/README.md
-    no-console:
-        - 2
-        -
-            allow:
-                - "warn"
-                - "error"
-    prefer-const: 1
-    no-constant-condition: 0
-    comma-dangle: 2
-    no-debugger: 2
-    no-dupe-keys: 2
-    no-empty-character-class: 2
-    no-ex-assign: 2
-    no-extra-boolean-cast: 0
-    no-func-assign: 2
-    no-inner-declarations: 2
-    no-invalid-regexp: 2
-    no-negated-in-lhs: 2
-    no-obj-calls: 2
-    no-sparse-arrays: 2
-    no-unreachable: 2
-    use-isnan: 2
-    valid-typeof: 2
-    block-scoped-var: 2
-    curly:
-        - 2
-        - "all"
-    eqeqeq:
-        - 2
-        - "allow-null"
-    guard-for-in: 2
-    no-else-return: 0
-    no-labels:
-        - 2
-        -
-            allowLoop: true
-    no-eval: 2
-    no-extend-native: 2
-    no-extra-bind: 0
-    no-implied-eval: 2
-    no-iterator: 2
-    no-irregular-whitespace: 2
-    no-lone-blocks: 2
-    no-loop-func: 2
-    no-multi-str: 2
-    no-native-reassign: 2
-    no-new-wrappers: 2
-    no-octal: 2
-    no-octal-escape: 2
-    no-proto: 2
-    no-redeclare: 2
-    no-self-compare: 2
-    no-unneeded-ternary: 2
-    no-with: 2
-    radix: 2
-    wrap-iife:
-        - 2
-        - "any"
-    no-delete-var: 2
-    no-dupe-args: 2
-    no-duplicate-case: 2
-    no-label-var: 2
-    no-shadow-restricted-names: 2
-    no-undef: 2
-    no-undef-init: 2
-    "no-use-before-define": 0
-    brace-style:
-        - 2
-        - "stroustrup"
-        - {}
-    comma-spacing:
-        - 2
-        -
-            before: false
-            after: true
-    comma-style:
-        - 2
-        - "last"
-    new-parens: 2
-    no-array-constructor: 2
-    no-multi-spaces:
-        - 1
-        -
-            ignoreEOLComments: true
-            exceptions:
-                Property: true
-    no-new-object: 2
-    no-trailing-spaces: 2
-    no-extra-parens:
-        - 2
-        - "functions"
-    no-mixed-spaces-and-tabs: 2
-    one-var:
-        - 2
-        - "never"
-    operator-linebreak:
-        - 2
-        - "before"
-        -
-            overrides:
-                "=": "after"
-    "quotes":
-        - 2
-        - "single"
-    "semi":
-        - 2
-        - "always"
-    semi-spacing: 2
-    keyword-spacing: 2
-    key-spacing:
-        - 2
-        -
-            beforeColon: false
-            afterColon: true
-    "space-before-function-paren":
-        - 2
-        -
-            anonymous: "always"
-            named: "never"
-    space-before-blocks:
-        - 2
-        - "always"
-    computed-property-spacing:
-        - 2
-        - "never"
-    space-in-parens:
-        - 2
-        - "never"
-    space-unary-ops: 2
-    spaced-comment: 0
-
-    max-nested-callbacks:
-        - 1
-        - 5
-    max-depth:
-        - 1
-        - 6
-    max-len:
-        - 2
-        - 120
-        - 4
-        -
-            ignoreUrls: true
-            ignoreComments: true
-    max-params:
-        - 1
-        - 15
-
-    space-infix-ops: 2
-    dot-notation:
-        - 2
-        -
-            allowKeywords: true
-            allowPattern: "^catch$"
-
-    arrow-spacing: 2
-    constructor-super: 2
-    no-confusing-arrow:
-        - 2
-        -
-            allowParens: true
-    no-class-assign: 2
-    no-const-assign: 2
-    # no-dupe-class-members: 2
-    no-this-before-super: 0
-    no-duplicate-imports: 2
-    prefer-rest-params: 0
-    unicode-bom: 2
-    max-statements-per-line: 2
-
-    no-useless-constructor: 0
-    indent: ["warn", 4]
-
-    "func-call-spacing": "error"
-
-    "no-unused-vars":
-        - 1
-        -
-            vars: "local"
-            args: "none"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b862812..5e01c25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ public/vendors/echarts/map/raw
 /public/asset
 /public/data/option
 /public/data-gl/option
+/public/examples/js
 /e2e/package-lock.json
 
 tmp
diff --git a/.gitignore b/.prettierignore
similarity index 71%
copy from .gitignore
copy to .prettierignore
index b862812..84fc23f 100644
--- a/.gitignore
+++ b/.prettierignore
@@ -1,8 +1,16 @@
+public/data
+public/data-gl
+src/data
+src/data
+src/data
+
+
+
+# From git ignore
 node_modules
 release
 public/vendors/echarts/map/tool
 public/vendors/echarts/map/raw
-.DS_Store
 /public.zip
 /build/config.gypi
 /public-gh
@@ -14,7 +22,7 @@ public/vendors/echarts/map/raw
 /public/asset
 /public/data/option
 /public/data-gl/option
+/public/examples/js
 /e2e/package-lock.json
 
-tmp
-.webm
\ No newline at end of file
+tmp
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..3410e80
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+  "tabWidth": 2,
+  "semi": true,
+  "singleQuote": true,
+  "trailingComma": "none",
+  "printWidth": 80
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..1b6457c
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+  "editor.formatOnSave": true,
+  "editor.defaultFormatter": "esbenp.prettier-vscode"
+}
diff --git a/README.md b/README.md
index 5891e3f..d2c7314 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,6 @@ It will copy all the build resources to echarts-website/next/examples
 1. Update the URL of localEChartsMinJS in `common/config.js`
 2. Add `local=1` in URL. For example: `editor.html?c=area-basic&local=1`
 
-
 ## Edit example
 
 All test cases are in the `public/examples/ts` folder. The comment in the header
@@ -59,26 +58,27 @@ Most of examples are written in `TypeScript`. You need to comile it to `JavaScri
 npm run compile:example
 ```
 
-
 ## Some built-in features available in examples
 
 ### Import third-party library
 
 For example:
+
 ```js
 $.when(
-    $.getScript(ROOT_PATH + '/data/asset/js/xxxx.js'),
-    $.getScript('https://cdn.jsdelivr.net/npm/d3-contour@2.0.0/dist/d3-contour.jXs'),
+  $.getScript(ROOT_PATH + '/data/asset/js/xxxx.js'),
+  $.getScript(
+    'https://cdn.jsdelivr.net/npm/d3-contour@2.0.0/dist/d3-contour.jXs'
+  )
 ).done(function () {
-    // ...
+  // ...
 });
 ```
 
-
-
 ### Controller panel
 
 Use this code to enable controller panel for a example:
+
 ```js
 app.config = {
     aNameForTheSelectWidget: 'This is the initial value'
@@ -109,8 +109,8 @@ app.configParameters = {
 
 ```js
 app.onresize = function () {
-    // Do something.
-}
+  // Do something.
+};
 ```
 
 ### Get width and height of the chart area
@@ -120,7 +120,6 @@ var width = myChart.getWidth();
 var height = myChart.getHeight();
 ```
 
-
 ## Update example snapshots
 
 ```shell
@@ -133,7 +132,6 @@ Only for default theme
 node tool/build-example.js -t default
 ```
 
-
 ## Run e2e tests.
 
 Run all the examples to test package publishing and install, module importing, minimal bundling and DTS correctness.
@@ -145,6 +143,7 @@ npm run build:example
 ```
 
 Then run the tests.
+
 ```shell
 npm run test:e2e
 ```
@@ -152,6 +151,7 @@ npm run test:e2e
 You can change the testing branch or local dir, which is available when add `--local` in `e2e/config.js`
 
 If you want to test with esbuild bundler. Which is much faster.
+
 ```shell
 npm run test:e2e:esbuild
 ```
@@ -178,5 +178,3 @@ Specify matched tests.
 ```shell
 node e2e/main.js --skip npm --tests bar3D*
 ```
-
-
diff --git a/build/copyResource.js b/build/copyResource.js
index d36d55d..bfa378a 100644
--- a/build/copyResource.js
+++ b/build/copyResource.js
@@ -23,78 +23,91 @@ const projectDir = __dirname;
  */
 
 function initEnv() {
-    let envType = argv.env;
-    let isDev = argv.dev != null || argv.debug != null || envType === 'debug' || envType === 'dev';
-
-    if (isDev) {
-        console.warn('====================================================================');
-        console.warn('THIS IS IN DEV MODE');
-        console.warn('!!! Please input your local host in `config/env.dev.js` firstly !!!');
-        console.warn('====================================================================');
-        envType = 'dev';
-    }
-
-    if (!envType) {
-        throw new Error('--env MUST be specified');
-    }
-
-    let config = require('../config/env.' + envType);
-
-    if (isDev) {
-        console.warn('====================================================================');
-        console.warn('Please visit the website: ');
-        console.warn(config.host);
-        console.warn('====================================================================');
-    }
-
-    assert(path.isAbsolute(config.releaseDestDir));
-
-    config.envType = envType;
-
-    return config;
+  let envType = argv.env;
+  let isDev =
+    argv.dev != null ||
+    argv.debug != null ||
+    envType === 'debug' ||
+    envType === 'dev';
+
+  if (isDev) {
+    console.warn(
+      '===================================================================='
+    );
+    console.warn('THIS IS IN DEV MODE');
+    console.warn(
+      '!!! Please input your local host in `config/env.dev.js` firstly !!!'
+    );
+    console.warn(
+      '===================================================================='
+    );
+    envType = 'dev';
+  }
+
+  if (!envType) {
+    throw new Error('--env MUST be specified');
+  }
+
+  let config = require('../config/env.' + envType);
+
+  if (isDev) {
+    console.warn(
+      '===================================================================='
+    );
+    console.warn('Please visit the website: ');
+    console.warn(config.host);
+    console.warn(
+      '===================================================================='
+    );
+  }
+
+  assert(path.isAbsolute(config.releaseDestDir));
+
+  config.envType = envType;
+
+  return config;
 }
 
 async function copyResourcesToDest(config) {
-    let basePath = path.resolve(projectDir, '../public');
-    const filePaths = await globby([
-        '**/*',
-        // Use jade in echarts-www
-        '!zh/*',
-        '!en/*'
-    ], {
-        cwd: basePath
-    });
-
-    console.log();
-
-    for (let filePath of filePaths) {
-        fse.ensureDirSync(
-            path.resolve(config.releaseDestDir, path.dirname(filePath))
-        );
-        let destPath = path.resolve(config.releaseDestDir, filePath);
-        fs.copyFileSync(
-            path.resolve(basePath, filePath),
-            destPath
-        );
-
-        if (process.stdout.clearLine) {
-            process.stdout.clearLine();
-            process.stdout.cursorTo(0);
-            process.stdout.write(chalk.green(`resource copied to: ${destPath}`));
-        }
-        else {
-            console.log(chalk.green(`resource copied to: ${destPath}`));
-        }
+  let basePath = path.resolve(projectDir, '../public');
+  const filePaths = await globby(
+    [
+      '**/*',
+      // Use jade in echarts-www
+      '!zh/*',
+      '!en/*'
+    ],
+    {
+      cwd: basePath
+    }
+  );
+
+  console.log();
+
+  for (let filePath of filePaths) {
+    fse.ensureDirSync(
+      path.resolve(config.releaseDestDir, path.dirname(filePath))
+    );
+    let destPath = path.resolve(config.releaseDestDir, filePath);
+    fs.copyFileSync(path.resolve(basePath, filePath), destPath);
+
+    if (process.stdout.clearLine) {
+      process.stdout.clearLine();
+      process.stdout.cursorTo(0);
+      process.stdout.write(chalk.green(`resource copied to: ${destPath}`));
+    } else {
+      console.log(chalk.green(`resource copied to: ${destPath}`));
     }
+  }
 
-    console.log('\ncopyResourcesToDest done.');
+  console.log('\ncopyResourcesToDest done.');
 }
 
 async function run() {
-    let config = initEnv();
-    await copyResourcesToDest(config);
+  let config = initEnv();
+  await copyResourcesToDest(config);
 
-    console.log('All done.');
+  console.log('All done.');
 }
 
 run();
diff --git a/build/server.js b/build/server.js
index 77b0da6..03867d2 100644
--- a/build/server.js
+++ b/build/server.js
@@ -2,19 +2,23 @@ const nStatic = require('node-static');
 const open = require('open');
 
 const fileServer = new nStatic.Server(__dirname + '/../public');
-require('http').createServer(function (request, response) {
-    request.addListener('end', function () {
+require('http')
+  .createServer(function (request, response) {
+    request
+      .addListener('end', function () {
         fileServer.serve(request, response);
-    }).resume();
-}).listen(3002);
+      })
+      .resume();
+  })
+  .listen(3002);
 
 // Wait bundling to be finished
 setTimeout(() => {
-    open('http://127.0.0.1:3002/en/index.html');
+  open('http://127.0.0.1:3002/en/index.html');
 }, 3000);
 
-process.on('SIGINT', function() {
-    console.log('Closing');
-    // Close through ctrl + c;
-    process.exit();
-});
\ No newline at end of file
+process.on('SIGINT', function () {
+  console.log('Closing');
+  // Close through ctrl + c;
+  process.exit();
+});
diff --git a/build/webpack.config.js b/build/webpack.config.js
index 3107a0c..80dd631 100644
--- a/build/webpack.config.js
+++ b/build/webpack.config.js
@@ -6,81 +6,94 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
 const distPath = path.resolve(__dirname, '../public');
 
 module.exports = [
-    {
-        entry: path.resolve(__dirname, '../src/main.js'),
-        output: {
-            publicPath: './',
-            filename: 'example-bundle.js',
-            path: path.join(distPath, 'js'),
-            library: 'echartsExample',
-            libraryTarget: 'var'
+  {
+    entry: path.resolve(__dirname, '../src/main.js'),
+    output: {
+      publicPath: './',
+      filename: 'example-bundle.js',
+      path: path.join(distPath, 'js'),
+      library: 'echartsExample',
+      libraryTarget: 'var'
+    },
+    stats: 'minimal',
+    module: {
+      rules: [
+        {
+          test: /\.vue$/,
+          use: ['vue-loader']
         },
-        stats: 'minimal',
-        module: {
-            rules: [{
-                test: /\.vue$/,
-                use: ['vue-loader']
-            }, {
-                test: /\.js$/,
-                use: ['babel-loader'],
-                exclude: /node_modules/
-            }, {
-                test: /\.css$/,
-                use: [MiniCssExtractPlugin.loader, 'css-loader']
-            }, {
-                test: /\.scss$/,
-                use: [MiniCssExtractPlugin.loader, 'css-loader', 'sassjs-loader']
-            }, {
-                test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2)(\?.+)?$/,
-                use: [{
-                    loader: 'file-loader',
-                    options: {
-                        limit: 10000,
-                        outputPath: '../asset',
-                        name: '[name].[ext]'
-                    }
-                }]
-            }, {
-                test: /\.svg$/,
-                use: [{
-                    loader: 'html-loader',
-                    options: {
-                        minimize: true
-                    }
-                }]
-              },]
+        {
+          test: /\.js$/,
+          use: ['babel-loader'],
+          exclude: /node_modules/
         },
-        externals: {
-            vue: 'Vue'
+        {
+          test: /\.css$/,
+          use: [MiniCssExtractPlugin.loader, 'css-loader']
         },
-        plugins: [
-            new webpack.IgnorePlugin(/^fs$/),
-            new VueLoaderPlugin(),
-            new MiniCssExtractPlugin({
-                filename: '../css/example-bundle.css'
-            })
-        ]
-    },
-    {
-        // Sepearte built ts transformer to be loaded async
-        entry: path.resolve(__dirname, '../src/editor/transformTs.js'),
-        stats: 'minimal',
-        module: {
-            rules: [{
-                test: /\.m?js$/,
-                include: /node_modules/,
-                type: 'javascript/auto',
-                resolve: {
-                    fullySpecified: false
-                }
-            }]
+        {
+          test: /\.scss$/,
+          use: [MiniCssExtractPlugin.loader, 'css-loader', 'sassjs-loader']
         },
-        output: {
-            filename: 'example-transform-ts-bundle.js',
-            path: path.join(distPath, 'js'),
-            library: 'echartsExampleTransformTs',
-            libraryExport: 'default',
-            libraryTarget: 'var'
+        {
+          test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2)(\?.+)?$/,
+          use: [
+            {
+              loader: 'file-loader',
+              options: {
+                limit: 10000,
+                outputPath: '../asset',
+                name: '[name].[ext]'
+              }
+            }
+          ]
+        },
+        {
+          test: /\.svg$/,
+          use: [
+            {
+              loader: 'html-loader',
+              options: {
+                minimize: true
+              }
+            }
+          ]
         }
+      ]
+    },
+    externals: {
+      vue: 'Vue'
+    },
+    plugins: [
+      new webpack.IgnorePlugin(/^fs$/),
+      new VueLoaderPlugin(),
+      new MiniCssExtractPlugin({
+        filename: '../css/example-bundle.css'
+      })
+    ]
+  },
+  {
+    // Sepearte built ts transformer to be loaded async
+    entry: path.resolve(__dirname, '../src/editor/transformTs.js'),
+    stats: 'minimal',
+    module: {
+      rules: [
+        {
+          test: /\.m?js$/,
+          include: /node_modules/,
+          type: 'javascript/auto',
+          resolve: {
+            fullySpecified: false
+          }
+        }
+      ]
+    },
+    output: {
+      filename: 'example-transform-ts-bundle.js',
+      path: path.join(distPath, 'js'),
+      library: 'echartsExampleTransformTs',
+      libraryExport: 'default',
+      libraryTarget: 'var'
     }
+  }
 ];
diff --git a/common/buildCode.js b/common/buildCode.js
index f0f5afa..64e4204 100644
--- a/common/buildCode.js
+++ b/common/buildCode.js
@@ -1,84 +1,83 @@
 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'
-}
+  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'
-}
+  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',
-}
+  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'
-}
+  bar3D: 'Bar3DChart',
+  line3D: 'Line3DChart',
+  scatter3D: 'Scatter3DChart',
+  lines3D: 'Lines3DChart',
+  polygons3D: 'Polygons3DChart',
+  surface: 'SurfaceChart',
+  map3D: 'Map3DChart',
+
+  scatterGL: 'ScatterGLChart',
+  graphGL: 'GraphGLChart',
+  flowGL: 'FlowGLChart',
+  linesGL: 'LinesGLChart'
+};
 
 const COMPONENTS_MAP_REVERSE = {};
 const CHARTS_MAP_REVERSE = {};
@@ -86,32 +85,39 @@ const CHARTS_GL_MAP_REVERSE = {};
 const COMPONENTS_GL_MAP_REVERSE = {};
 
 const RENDERERS_MAP_REVERSE = {
-    'SVGRenderer': 'svg',
-    'CanvasRenderer': 'canvas'
-}
+  SVGRenderer: 'svg',
+  CanvasRenderer: 'canvas'
+};
 
 // 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
+  ...MARKERS,
+  'grid',
+  'axisPointer',
+  'aria' // TODO aria
 ];
 
 // Component that was dependent.
 const DEPENDENT_COMPONENTS = [
-    'xAxis', 'yAxis', 'angleAxis', 'radiusAxis',
-    'xAxis3D', 'yAxis3D', 'zAxis3D'
+  'xAxis',
+  'yAxis',
+  'angleAxis',
+  'radiusAxis',
+  'xAxis3D',
+  'yAxis3D',
+  'zAxis3D'
 ];
 
 function createReverseMap(map, reverseMap) {
-    Object.keys(map).forEach(key => {
-        // Exclude dependencies.
-        if (DEPENDENT_COMPONENTS.includes(key)) {
-            return;
-        }
-        reverseMap[map[key]] = key;
-    });
+  Object.keys(map).forEach((key) => {
+    // Exclude dependencies.
+    if (DEPENDENT_COMPONENTS.includes(key)) {
+      return;
+    }
+    reverseMap[map[key]] = key;
+  });
 }
 
 createReverseMap(COMPONENTS_MAP, COMPONENTS_MAP_REVERSE);
@@ -120,248 +126,273 @@ createReverseMap(COMPONENTS_GL_MAP, COMPONENTS_GL_MAP_REVERSE);
 createReverseMap(CHARTS_GL_MAP, CHARTS_GL_MAP_REVERSE);
 
 module.exports.collectDeps = function collectDeps(option) {
-    let deps = [];
-    if (option.options) {
+  let deps = [];
+  if (option.options) {
+    // TODO getOption() doesn't have baseOption and options.
+    option.options.forEach((opt) => {
+      deps = deps.concat(collectDeps(opt));
+    });
 
-        // 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));
+    }
 
-        if (option.baseOption) {
-            deps = deps.concat(collectDeps(option.baseOption))
-        }
+    // Remove duplicates
+    return Array.from(new Set(deps));
+  }
 
-        // Remove duplicates
-        return Array.from(new Set(deps));
+  Object.keys(option).forEach((key) => {
+    if (INJECTED_COMPONENTS.includes(key)) {
+      return;
     }
+    const val = option[key];
 
-    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 (Array.isArray(val) && !val.length) {
+      return;
+    }
 
-    let series = option.series;
-    if (!Array.isArray(series)) {
-        series = [series];
+    if (COMPONENTS_MAP[key]) {
+      deps.push(COMPONENTS_MAP[key]);
     }
+    if (COMPONENTS_GL_MAP[key]) {
+      deps.push(COMPONENTS_GL_MAP[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);
-        }
-        MARKERS.forEach(markerType => {
-            if (seriesOpt[markerType]) {
-                deps.push(COMPONENTS_MAP[markerType]);
-            }
-        });
+  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);
+    }
+    MARKERS.forEach((markerType) => {
+      if (seriesOpt[markerType]) {
+        deps.push(COMPONENTS_MAP[markerType]);
+      }
     });
+  });
 
-    // Remove duplicates
-    return Array.from(new Set(deps));
-}
+  // Remove duplicates
+  return Array.from(new Set(deps));
+};
 
 function buildMinimalBundleCode(deps, includeType) {
-    const chartsImports = [];
-    const componentsImports = [];
-    const chartsGLImports = [];
-    const componentsGLImports = [];
-    const renderersImports = [];
-    deps.forEach(function (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 (CHARTS_GL_MAP_REVERSE[dep]) {
-            chartsGLImports.push(dep)
-        }
-        else if (COMPONENTS_GL_MAP_REVERSE[dep]) {
-            componentsGLImports.push(dep);
-        }
-    });
-
-    function getImportsPartCode(imports) {
-        return `${imports.map(str => `
-    ${str}`).join(',')}`;
+  const chartsImports = [];
+  const componentsImports = [];
+  const chartsGLImports = [];
+  const componentsGLImports = [];
+  const renderersImports = [];
+  deps.forEach(function (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 (CHARTS_GL_MAP_REVERSE[dep]) {
+      chartsGLImports.push(dep);
+    } else if (COMPONENTS_GL_MAP_REVERSE[dep]) {
+      componentsGLImports.push(dep);
     }
-
-    const allImports = [
-        ...componentsImports,
-        ...chartsImports,
-        ...componentsGLImports,
-        ...chartsGLImports,
-        ...renderersImports
-    ];
-
-    const ECOptionTypeCode = `
+  });
+
+  function getImportsPartCode(imports) {
+    return `${imports
+      .map(
+        (str) => `
+    ${str}`
+      )
+      .join(',')}`;
+  }
+
+  const allImports = [
+    ...componentsImports,
+    ...chartsImports,
+    ...componentsGLImports,
+    ...chartsGLImports,
+    ...renderersImports
+  ];
+
+  const ECOptionTypeCode = `
 type ECOption = echarts.ComposeOption<
-    ${allImports.filter(a => a.endsWith('Option')).join(' | ')}
+    ${allImports.filter((a) => a.endsWith('Option')).join(' | ')}
 >`;
-    const importsCodes = [
-        [componentsImports, 'echarts/components'],
-        [chartsImports, 'echarts/charts'],
-        [renderersImports, 'echarts/renderers'],
-        [chartsGLImports, 'echarts-gl/charts'],
-        [componentsGLImports, 'echarts-gl/components']
-    ].filter(a => a[0].length > 0).map(item => `
+  const importsCodes = [
+    [componentsImports, 'echarts/components'],
+    [chartsImports, 'echarts/charts'],
+    [renderersImports, 'echarts/renderers'],
+    [chartsGLImports, 'echarts-gl/charts'],
+    [componentsGLImports, 'echarts-gl/components']
+  ]
+    .filter((a) => a[0].length > 0)
+    .map((item) =>
+      `
 import {${getImportsPartCode(item[0])}
 } from '${item[1]}';
-    `.trim()).join('\n');
+    `.trim()
+    )
+    .join('\n');
 
-    return `import * as echarts from 'echarts/core';
+  return (
+    `import * as echarts from 'echarts/core';
 ${importsCodes}
 
 echarts.use(
-    [${allImports.filter(a => !a.endsWith('Option')).join(', ')}]
+    [${allImports.filter((a) => !a.endsWith('Option')).join(', ')}]
 );
 ` + (includeType ? ECOptionTypeCode : '')
+  );
 }
 
 module.exports.buildMinimalBundleCode = buildMinimalBundleCode;
 
 function buildLegacyMinimalBundleCode(deps, isESM) {
-    const modules = [];
-    deps.forEach(function (dep) {
-        if (dep.endsWith('Renderer') && dep !== 'CanvasRenderer') {
-            modules.push(`zrender/lib/${RENDERERS_MAP_REVERSE[dep]}/${RENDERERS_MAP_REVERSE[dep]}`);
-        }
-        else if (CHARTS_MAP_REVERSE[dep]) {
-            modules.push(`echarts/lib/chart/${CHARTS_MAP_REVERSE[dep]}`);
-        }
-        else if (COMPONENTS_MAP_REVERSE[dep]) {
-            modules.push(`echarts/lib/component/${COMPONENTS_MAP_REVERSE[dep]}`);
-        }
-        else if (CHARTS_GL_MAP_REVERSE[dep]) {
-            modules.push(`echarts-gl/lib/chart/${CHARTS_GL_MAP_REVERSE[dep]}`);
-        }
-        else if (COMPONENTS_GL_MAP_REVERSE[dep]) {
-            modules.push(`echarts-gl/lib/component/${COMPONENTS_GL_MAP_REVERSE[dep]}`);
-        }
-    });
+  const modules = [];
+  deps.forEach(function (dep) {
+    if (dep.endsWith('Renderer') && dep !== 'CanvasRenderer') {
+      modules.push(
+        `zrender/lib/${RENDERERS_MAP_REVERSE[dep]}/${RENDERERS_MAP_REVERSE[dep]}`
+      );
+    } else if (CHARTS_MAP_REVERSE[dep]) {
+      modules.push(`echarts/lib/chart/${CHARTS_MAP_REVERSE[dep]}`);
+    } else if (COMPONENTS_MAP_REVERSE[dep]) {
+      modules.push(`echarts/lib/component/${COMPONENTS_MAP_REVERSE[dep]}`);
+    } else if (CHARTS_GL_MAP_REVERSE[dep]) {
+      modules.push(`echarts-gl/lib/chart/${CHARTS_GL_MAP_REVERSE[dep]}`);
+    } else if (COMPONENTS_GL_MAP_REVERSE[dep]) {
+      modules.push(
+        `echarts-gl/lib/component/${COMPONENTS_GL_MAP_REVERSE[dep]}`
+      );
+    }
+  });
 
-    return isESM ? `import * as echarts from 'echarts/lib/echarts';
-${modules.map(mod => {
+  return isESM
+    ? `import * as echarts from 'echarts/lib/echarts';
+${modules
+  .map((mod) => {
     return `import '${mod}';`;
-}).join('\n')}
-` : `const echarts = require('echarts/lib/echarts');
-${modules.map(mod => {
-    return `require('${mod}');`;
-}).join('\n')}
+  })
+  .join('\n')}
 `
+    : `const echarts = require('echarts/lib/echarts');
+${modules
+  .map((mod) => {
+    return `require('${mod}');`;
+  })
+  .join('\n')}
+`;
 }
 
 function hasGLInDeps(deps) {
-    return !!deps.find(dep => !!(CHARTS_GL_MAP_REVERSE[dep] || COMPONENTS_GL_MAP_REVERSE[dep]));
+  return !!deps.find(
+    (dep) => !!(CHARTS_GL_MAP_REVERSE[dep] || COMPONENTS_GL_MAP_REVERSE[dep])
+  );
 }
 
 module.buildLegacyMinimalBundleCode = buildLegacyMinimalBundleCode;
 
 module.exports.buildExampleCode = function (
-    jsCode, deps, {
-        // If enable minimal import
-        minimal,
-        // If is ESM module or CommonJS module
-        // Force to be true in ts mode or minimal mode.
-        esm = true,
-        // If use legacy minimal import, like:
-        // import 'echarts/lib/chart/bar';
-        // Only available when minimal is true.
-        legacy,
-        // If is ts code
-        ts,
-        // Theme
-        theme,
-        ROOT_PATH,
-        // Other imports code code string
-        // For example
-        // `import 'echarts-liquidfill'`
-        extraImports
-    }
+  jsCode,
+  deps,
+  {
+    // If enable minimal import
+    minimal,
+    // If is ESM module or CommonJS module
+    // Force to be true in ts mode or minimal mode.
+    esm = true,
+    // If use legacy minimal import, like:
+    // import 'echarts/lib/chart/bar';
+    // Only available when minimal is true.
+    legacy,
+    // If is ts code
+    ts,
+    // Theme
+    theme,
+    ROOT_PATH,
+    // Other imports code code string
+    // For example
+    // `import 'echarts-liquidfill'`
+    extraImports
+  }
 ) {
-    // if (minimal && !legacy) {
-    //     // ESM must be used when use the new minimal import
-    //     esm = true;
-    // }
-
-    if (ts) {
-        esm = true;
-    }
-
-    if (minimal && !esm) {
-        // Only legacy mode can be used when use require in mimimal bundle.
-        legacy = true;
-    }
-
-
-    const hasECStat = jsCode.indexOf('ecStat') >= 0;
-    const usedRootPath = jsCode.indexOf('ROOT_PATH') >= 0;
-    const usedApp = jsCode.indexOf('app') >= 0;
-
-    const DEP_CODE = `
-${hasECStat ?
-    esm ? `import ecStat from 'echarts-stat';`
-        : `var ecStat = require('echarts-stat');`
+  // if (minimal && !legacy) {
+  //     // ESM must be used when use the new minimal import
+  //     esm = true;
+  // }
+
+  if (ts) {
+    esm = true;
+  }
+
+  if (minimal && !esm) {
+    // Only legacy mode can be used when use require in mimimal bundle.
+    legacy = true;
+  }
+
+  const hasECStat = jsCode.indexOf('ecStat') >= 0;
+  const usedRootPath = jsCode.indexOf('ROOT_PATH') >= 0;
+  const usedApp = jsCode.indexOf('app') >= 0;
+
+  const DEP_CODE = `
+${
+  hasECStat
+    ? esm
+      ? `import ecStat from 'echarts-stat';`
+      : `var ecStat = require('echarts-stat');`
     : ''
 }
 `;
-    const IMPORT_CODE = [
-        !minimal
-            ? esm
-                ? `import * as echarts from 'echarts';${hasGLInDeps(deps) ? `\nimport 'echarts-gl';` : ''}`
-                : `var echarts = require('echarts');${hasGLInDeps(deps) ? `\nrequire('echarts-gl');` : ''}`
-            : legacy
-                ? buildLegacyMinimalBundleCode(deps, esm)
-                : buildMinimalBundleCode(deps, ts),
-        (theme && theme !== 'dark')
-            ? esm
-                ? `import 'echarts/theme/${theme}'`
-                : `require('echarts/theme/${theme}')`
-            : '',
-        extraImports
-    ].filter(a => !!a).join('\n');
-
-    const ENV_CODE = [
-        usedRootPath ? `var ROOT_PATH = '${ROOT_PATH}';` : '',
-        usedApp ? `var app${ts ? ': any' : ''} = {};` : '',
-        ts && !minimal ? 'type ECOption = echarts.EChartsOption' : ''
-    ].filter(a => !!a).join('\n');
-
-    const PREPARE_CODE = [
-        IMPORT_CODE.trim(), DEP_CODE.trim(), ENV_CODE.trim()
-    ].filter(a => !!a).join('\n\n');
-
-    return `${PREPARE_CODE}
+  const IMPORT_CODE = [
+    !minimal
+      ? esm
+        ? `import * as echarts from 'echarts';${
+            hasGLInDeps(deps) ? `\nimport 'echarts-gl';` : ''
+          }`
+        : `var echarts = require('echarts');${
+            hasGLInDeps(deps) ? `\nrequire('echarts-gl');` : ''
+          }`
+      : legacy
+      ? buildLegacyMinimalBundleCode(deps, esm)
+      : buildMinimalBundleCode(deps, ts),
+    theme && theme !== 'dark'
+      ? esm
+        ? `import 'echarts/theme/${theme}'`
+        : `require('echarts/theme/${theme}')`
+      : '',
+    extraImports
+  ]
+    .filter((a) => !!a)
+    .join('\n');
+
+  const ENV_CODE = [
+    usedRootPath ? `var ROOT_PATH = '${ROOT_PATH}';` : '',
+    usedApp ? `var app${ts ? ': any' : ''} = {};` : '',
+    ts && !minimal ? 'type ECOption = echarts.EChartsOption' : ''
+  ]
+    .filter((a) => !!a)
+    .join('\n');
+
+  const PREPARE_CODE = [IMPORT_CODE.trim(), DEP_CODE.trim(), ENV_CODE.trim()]
+    .filter((a) => !!a)
+    .join('\n\n');
+
+  return `${PREPARE_CODE}
 
 var chartDom = document.getElementById('main')${ts ? '!' : ''};
 var myChart = echarts.init(chartDom${theme ? `, '${theme}'` : ''});
@@ -371,4 +402,4 @@ ${jsCode.trim()}
 
 option && myChart.setOption(option);
 `;
-}
\ No newline at end of file
+};
diff --git a/common/compareImage.js b/common/compareImage.js
index e0d3a93..1515933 100644
--- a/common/compareImage.js
+++ b/common/compareImage.js
@@ -3,45 +3,52 @@ const PNG = require('pngjs').PNG;
 const pixelmatch = require('pixelmatch');
 
 function readPNG(path) {
-    return new Promise(resolve => {
-        fs.createReadStream(path)
-            .pipe(new PNG())
-            .on('parsed', function () {
-                resolve({
-                    data: this.data,
-                    width: this.width,
-                    height: this.height
-                });
-            });
-    });
+  return new Promise((resolve) => {
+    fs.createReadStream(path)
+      .pipe(new PNG())
+      .on('parsed', function () {
+        resolve({
+          data: this.data,
+          width: this.width,
+          height: this.height
+        });
+      });
+  });
 }
 
-module.exports.compareImage = function (targetPath, sourcePath, threshold = 0.1) {
-    if (!fs.existsSync(targetPath)) {
-        return true;
-    }
+module.exports.compareImage = function (
+  targetPath,
+  sourcePath,
+  threshold = 0.1
+) {
+  if (!fs.existsSync(targetPath)) {
+    return true;
+  }
 
-    return Promise.all([
-        readPNG(targetPath),
-        readPNG(sourcePath)
-    ]).then(([expectedImg, actualImg]) => {
-        let width = expectedImg.width;
-        let height = expectedImg.height;
-        if (
-            (width !== actualImg.width)
-          || (height !== actualImg.height)
-        ) {
-            return {diffRatio: 1}
+  return Promise.all([readPNG(targetPath), readPNG(sourcePath)]).then(
+    ([expectedImg, actualImg]) => {
+      let width = expectedImg.width;
+      let height = expectedImg.height;
+      if (width !== actualImg.width || height !== actualImg.height) {
+        return { diffRatio: 1 };
+      }
+      const diffPNG = new PNG({ width, height });
+      let diffPixelsCount = pixelmatch(
+        expectedImg.data,
+        actualImg.data,
+        diffPNG.data,
+        width,
+        height,
+        {
+          threshold
         }
-        const diffPNG = new PNG({width, height});
-        let diffPixelsCount = pixelmatch(expectedImg.data, actualImg.data, diffPNG.data, width, height, {
-            threshold
-        });
-        let totalPixelsCount = width * height;
+      );
+      let totalPixelsCount = width * height;
 
-        return {
-            diffRatio: diffPixelsCount / totalPixelsCount,
-            diffPNG
-        };
-    });
-}
\ No newline at end of file
+      return {
+        diffRatio: diffPixelsCount / totalPixelsCount,
+        diffPNG
+      };
+    }
+  );
+};
diff --git a/common/task.js b/common/task.js
index f2e5f1c..4ad82ce 100644
--- a/common/task.js
+++ b/common/task.js
@@ -1,51 +1,49 @@
-function runTasks(
-    taskParamsLists, createTask, concurrency
-) {
-    concurrency = Math.min(taskParamsLists.length, concurrency);
-    return new Promise((resolve, reject) => {
-        let runningTaskCount = 0;
-        let cursor = 0;
-        let rets = [];
-
-        function finishTask(res, idx) {
-            rets[idx] = res;
-            processNext();
-        }
-
-        function failTask(e) {
-            console.error(e);
-            processNext();
-        }
-
-        function processNext() {
-            runningTaskCount--;
-            addTask();
-
-            if (runningTaskCount === 0) {
-                resolve(rets);
-            }
-        }
-
-        function addTask() {
-            const param = taskParamsLists[cursor];
-            if (param) {
-                const currentTaskIdx = cursor;
-                runningTaskCount++;
-                createTask(param)
-                    .then((res) => finishTask(res, currentTaskIdx))
-                    .catch(failTask);
-                cursor++;
-            }
-        }
-
-        for (let i = 0; i < concurrency; i++) {
-            addTask();
-        }
-
-        if (!runningTaskCount) {
-            resolve(rets);
-        }
-    });
+function runTasks(taskParamsLists, createTask, concurrency) {
+  concurrency = Math.min(taskParamsLists.length, concurrency);
+  return new Promise((resolve, reject) => {
+    let runningTaskCount = 0;
+    let cursor = 0;
+    let rets = [];
+
+    function finishTask(res, idx) {
+      rets[idx] = res;
+      processNext();
+    }
+
+    function failTask(e) {
+      console.error(e);
+      processNext();
+    }
+
+    function processNext() {
+      runningTaskCount--;
+      addTask();
+
+      if (runningTaskCount === 0) {
+        resolve(rets);
+      }
+    }
+
+    function addTask() {
+      const param = taskParamsLists[cursor];
+      if (param) {
+        const currentTaskIdx = cursor;
+        runningTaskCount++;
+        createTask(param)
+          .then((res) => finishTask(res, currentTaskIdx))
+          .catch(failTask);
+        cursor++;
+      }
+    }
+
+    for (let i = 0; i < concurrency; i++) {
+      addTask();
+    }
+
+    if (!runningTaskCount) {
+      resolve(rets);
+    }
+  });
 }
 
-module.exports.runTasks = runTasks;
\ No newline at end of file
+module.exports.runTasks = runTasks;
diff --git a/config/common.js b/config/common.js
index e4d473f..f053ebf 100644
--- a/config/common.js
+++ b/config/common.js
@@ -1,5 +1 @@
-
-module.exports = {
-};
-
-
+module.exports = {};
diff --git a/config/env.asf.js b/config/env.asf.js
index 8c08547..16d9a2e 100644
--- a/config/env.asf.js
+++ b/config/env.asf.js
@@ -2,7 +2,7 @@ const path = require('path');
 const config = require('./common');
 
 Object.assign(config, {
-    releaseDestDir: path.resolve(__dirname, '../../echarts-website/examples')
+  releaseDestDir: path.resolve(__dirname, '../../echarts-website/examples')
 });
 
 module.exports = config;
diff --git a/config/env.dev.js b/config/env.dev.js
index 8c08547..16d9a2e 100644
--- a/config/env.dev.js
+++ b/config/env.dev.js
@@ -2,7 +2,7 @@ const path = require('path');
 const config = require('./common');
 
 Object.assign(config, {
-    releaseDestDir: path.resolve(__dirname, '../../echarts-website/examples')
+  releaseDestDir: path.resolve(__dirname, '../../echarts-website/examples')
 });
 
 module.exports = config;
diff --git a/config/env.localsite.js b/config/env.localsite.js
index 84106f2..b8fdaaf 100644
--- a/config/env.localsite.js
+++ b/config/env.localsite.js
@@ -2,7 +2,7 @@ const path = require('path');
 const config = require('./common');
 
 Object.assign(config, {
-    releaseDestDir: path.resolve(__dirname, '../../echarts-website/next/examples')
+  releaseDestDir: path.resolve(__dirname, '../../echarts-website/next/examples')
 });
 
 module.exports = config;
diff --git a/e2e/cases/README.md b/e2e/cases/README.md
index 7d12477..57c6a61 100644
--- a/e2e/cases/README.md
+++ b/e2e/cases/README.md
@@ -1,3 +1,3 @@
 # Handwritten Cases
 
-Mostly for testing extensions.
\ No newline at end of file
+Mostly for testing extensions.
diff --git a/e2e/cases/liquidfill.js b/e2e/cases/liquidfill.js
index c219859..e257bb4 100644
--- a/e2e/cases/liquidfill.js
+++ b/e2e/cases/liquidfill.js
@@ -2,9 +2,11 @@
 extension: echarts-liquidfill
 */
 option = {
-    series: [{
-        type: 'liquidFill',
-        data: [0.6],
-        radius: '70%'
-    }]
-};
\ No newline at end of file
+  series: [
+    {
+      type: 'liquidFill',
+      data: [0.6],
+      radius: '70%'
+    }
+  ]
+};
diff --git a/e2e/cases/wordcloud.js b/e2e/cases/wordcloud.js
index ee14f29..5c01bb8 100644
--- a/e2e/cases/wordcloud.js
+++ b/e2e/cases/wordcloud.js
@@ -2,120 +2,126 @@
 extension: echarts-wordcloud
 */
 option = {
-    tooltip: {},
-    series: [ {
-        type: 'wordCloud',
-        gridSize: 2,
-        sizeRange: [12, 50],
-        rotationRange: [-90, 90],
-        shape: 'pentagon',
-        width: 600,
-        height: 400,
-        drawOutOfBound: true,
+  tooltip: {},
+  series: [
+    {
+      type: 'wordCloud',
+      gridSize: 2,
+      sizeRange: [12, 50],
+      rotationRange: [-90, 90],
+      shape: 'pentagon',
+      width: 600,
+      height: 400,
+      drawOutOfBound: true,
+      textStyle: {
+        color: function () {
+          return (
+            'rgb(' +
+            [
+              Math.round(Math.random() * 160),
+              Math.round(Math.random() * 160),
+              Math.round(Math.random() * 160)
+            ].join(',') +
+            ')'
+          );
+        }
+      },
+      emphasis: {
         textStyle: {
-            color: function () {
-                return 'rgb(' + [
-                    Math.round(Math.random() * 160),
-                    Math.round(Math.random() * 160),
-                    Math.round(Math.random() * 160)
-                ].join(',') + ')';
-            }
-        },
-        emphasis: {
+          shadowBlur: 10,
+          shadowColor: '#333'
+        }
+      },
+      data: [
+        {
+          name: 'Sam S Club',
+          value: 10000,
+          textStyle: {
+            color: 'black'
+          },
+          emphasis: {
             textStyle: {
-                shadowBlur: 10,
-                shadowColor: '#333'
+              color: 'red'
             }
+          }
         },
-        data: [
-            {
-                name: 'Sam S Club',
-                value: 10000,
-                textStyle: {
-                    color: 'black'
-                },
-                emphasis: {
-                    textStyle: {
-                        color: 'red'
-                    }
-                }
-            },
-            {
-                name: 'Macys',
-                value: 6181
-            },
-            {
-                name: 'Amy Schumer',
-                value: 4386
-            },
-            {
-                name: 'Jurassic World',
-                value: 4055
-            },
-            {
-                name: 'Charter Communications',
-                value: 2467
-            },
-            {
-                name: 'Chick Fil A',
-                value: 2244
-            },
-            {
-                name: 'Planet Fitness',
-                value: 1898
-            },
-            {
-                name: 'Pitch Perfect',
-                value: 1484
-            },
-            {
-                name: 'Express',
-                value: 1112
-            },
-            {
-                name: 'Home',
-                value: 965
-            },
-            {
-                name: 'Johnny Depp',
-                value: 847
-            },
-            {
-                name: 'Lena Dunham',
-                value: 582
-            },
-            {
-                name: 'Lewis Hamilton',
-                value: 555
-            },
-            {
-                name: 'KXAN',
-                value: 550
-            },
-            {
-                name: 'Mary Ellen Mark',
-                value: 462
-            },
-            {
-                name: 'Farrah Abraham',
-                value: 366
-            },
-            {
-                name: 'Rita Ora',
-                value: 360
-            },
-            {
-                name: 'Serena Williams',
-                value: 282
-            },
-            {
-                name: 'NCAA baseball tournament',
-                value: 273
-            },
-            {
-                name: 'Point Break',
-                value: 265
-            }
-        ]
-    } ]
+        {
+          name: 'Macys',
+          value: 6181
+        },
+        {
+          name: 'Amy Schumer',
+          value: 4386
+        },
+        {
+          name: 'Jurassic World',
+          value: 4055
+        },
+        {
+          name: 'Charter Communications',
+          value: 2467
+        },
+        {
+          name: 'Chick Fil A',
+          value: 2244
+        },
+        {
+          name: 'Planet Fitness',
+          value: 1898
+        },
+        {
+          name: 'Pitch Perfect',
+          value: 1484
+        },
+        {
+          name: 'Express',
+          value: 1112
+        },
+        {
+          name: 'Home',
+          value: 965
+        },
+        {
+          name: 'Johnny Depp',
+          value: 847
+        },
+        {
+          name: 'Lena Dunham',
+          value: 582
+        },
+        {
+          name: 'Lewis Hamilton',
+          value: 555
+        },
+        {
+          name: 'KXAN',
+          value: 550
+        },
+        {
+          name: 'Mary Ellen Mark',
+          value: 462
+        },
+        {
+          name: 'Farrah Abraham',
+          value: 366
+        },
+        {
+          name: 'Rita Ora',
+          value: 360
+        },
+        {
+          name: 'Serena Williams',
+          value: 282
+        },
+        {
+          name: 'NCAA baseball tournament',
+          value: 273
+        },
+        {
+          name: 'Point Break',
+          value: 265
+        }
+      ]
+    }
+  ]
 };
diff --git a/e2e/config.js b/e2e/config.js
index d377f17..61bbff2 100644
--- a/e2e/config.js
+++ b/e2e/config.js
@@ -1,32 +1,32 @@
 const nodePath = require('path');
 
 module.exports = {
-    packages: [
-        // Handwritten topological sort by the rule of dependency.
-        {
-            name: 'zrender',    // package name
-            dir: nodePath.resolve(__dirname, '../../zrender'),
-            git: 'ecomfe/zrender#release'
-        },
-        {
-            name: 'echarts',
-            dir: nodePath.resolve(__dirname, '../../echarts'),
-            git: 'apache/echarts#release'
-        },
-        {
-            name: 'echarts-gl',
-            dir: nodePath.resolve(__dirname, '../../echarts-gl'),
-            git: 'ecomfe/echarts-gl#master'
-        },
-        {
-            name: 'echarts-wordcloud',
-            dir: nodePath.resolve(__dirname, '../../echarts-wordcloud'),
-            git: 'ecomfe/echarts-wordcloud#master'
-        },
-        {
-            name: 'echarts-liquidfill',
-            dir: nodePath.resolve(__dirname, '../../echarts-liquidfill'),
-            git: 'ecomfe/echarts-liquidfill#master'
-        }
-    ]
-}
\ No newline at end of file
+  packages: [
+    // Handwritten topological sort by the rule of dependency.
+    {
+      name: 'zrender', // package name
+      dir: nodePath.resolve(__dirname, '../../zrender'),
+      git: 'ecomfe/zrender#release'
+    },
+    {
+      name: 'echarts',
+      dir: nodePath.resolve(__dirname, '../../echarts'),
+      git: 'apache/echarts#release'
+    },
+    {
+      name: 'echarts-gl',
+      dir: nodePath.resolve(__dirname, '../../echarts-gl'),
+      git: 'ecomfe/echarts-gl#master'
+    },
+    {
+      name: 'echarts-wordcloud',
+      dir: nodePath.resolve(__dirname, '../../echarts-wordcloud'),
+      git: 'ecomfe/echarts-wordcloud#master'
+    },
+    {
+      name: 'echarts-liquidfill',
+      dir: nodePath.resolve(__dirname, '../../echarts-liquidfill'),
+      git: 'ecomfe/echarts-liquidfill#master'
+    }
+  ]
+};
diff --git a/e2e/main.js b/e2e/main.js
index c086f26..acc8517 100644
--- a/e2e/main.js
+++ b/e2e/main.js
@@ -2,7 +2,7 @@
 
 const fs = require('fs');
 const globby = require('globby');
-const {buildExampleCode, collectDeps} = require('../common/buildCode');
+const { buildExampleCode, collectDeps } = require('../common/buildCode');
 const nodePath = require('path');
 const { runTasks } = require('../common/task');
 const fse = require('fs-extra');
@@ -11,37 +11,37 @@ const ts = require('typescript');
 const chalk = require('chalk');
 const nStatic = require('node-static');
 const webpack = require('webpack');
-const {RawSource} = require('webpack-sources');
+const { RawSource } = require('webpack-sources');
 const argparse = require('argparse');
 const esbuild = require('esbuild');
 const puppeteer = require('puppeteer');
 const config = require('./config');
-const {compareImage} = require('../common/compareImage');
+const { compareImage } = require('../common/compareImage');
 const shell = require('shelljs');
 const downloadGit = require('download-git-repo');
-const {promisify} = require('util');
+const { promisify } = require('util');
 const matter = require('gray-matter');
 const minimatch = require('minimatch');
 
 const parser = new argparse.ArgumentParser({
-    addHelp: true
+  addHelp: true
 });
 parser.addArgument(['--bundler'], {
-    help: 'Bundler, can be webpack or esbuild'
+  help: 'Bundler, can be webpack or esbuild'
 });
 parser.addArgument(['-m', '--minify'], {
-    action: 'storeTrue',
-    help: 'If minify'
+  action: 'storeTrue',
+  help: 'If minify'
 });
 parser.addArgument(['--local'], {
-    action: 'storeTrue',
-    help: `If use local repos. If so, don't forget to update the location of local repo in config.js.`
+  action: 'storeTrue',
+  help: `If use local repos. If so, don't forget to update the location of local repo in config.js.`
 });
 parser.addArgument(['--skip'], {
-    help: 'If skip some stages to speed up the test. Can be npm,bundle,render,compare'
+  help: 'If skip some stages to speed up the test. Can be npm,bundle,render,compare'
 });
 parser.addArgument(['-t', '--tests'], {
-    help: 'If use pattern to specify which tests to run'
+  help: 'If use pattern to specify which tests to run'
 });
 const args = parser.parseArgs();
 
@@ -63,7 +63,7 @@ const USE_WEBPACK = !(args.bundler === 'esbuild');
 
 let testsPattern = args.tests;
 if (testsPattern) {
-    testsPattern = testsPattern.split(',');
+  testsPattern = testsPattern.split(',');
 }
 
 // Create a server
@@ -89,21 +89,23 @@ echarts.registerPreprocessor(function (option) {
         });
     }
 });
-`
+`;
 
 function buildPrepareCode(isESM, lang) {
-    return `
+  return `
 // @ts-ignore
-${isESM
+${
+  isESM
     ? `import _seedrandom from 'seedrandom';`
     : `const _seedrandom = require('seedrandom');`
 }
 
 // Check if i18n will break the minimal imports.
-${lang
+${
+  lang
     ? isESM
-        ? `import 'echarts/i18n/${lang}';`
-        : `require('echarts/i18n/${lang}');`
+      ? `import 'echarts/i18n/${lang}';`
+      : `require('echarts/i18n/${lang}');`
     : ''
 }
 // @ts-ignore
@@ -118,657 +120,767 @@ ${TEMPLATE_CODE}
 }
 
 async function prepare() {
-    fse.removeSync(TMP_DIR);
-    fse.removeSync(RUN_CODE_DIR);
-    fse.removeSync(BUNDLE_DIR);
-    fse.removeSync(SCREENSHOTS_DIR);
+  fse.removeSync(TMP_DIR);
+  fse.removeSync(RUN_CODE_DIR);
+  fse.removeSync(BUNDLE_DIR);
+  fse.removeSync(SCREENSHOTS_DIR);
 
-    fse.removeSync(REPO_DIR);
-    fse.removeSync(PACKAGE_DIR);
+  fse.removeSync(REPO_DIR);
+  fse.removeSync(PACKAGE_DIR);
 
-    fse.ensureDirSync(TMP_DIR);
-    fse.ensureDirSync(RUN_CODE_DIR);
-    fse.ensureDirSync(BUNDLE_DIR);
-    fse.ensureDirSync(SCREENSHOTS_DIR);
+  fse.ensureDirSync(TMP_DIR);
+  fse.ensureDirSync(RUN_CODE_DIR);
+  fse.ensureDirSync(BUNDLE_DIR);
+  fse.ensureDirSync(SCREENSHOTS_DIR);
 
-    fse.ensureDirSync(REPO_DIR);
-    fse.ensureDirSync(PACKAGE_DIR);
+  fse.ensureDirSync(REPO_DIR);
+  fse.ensureDirSync(PACKAGE_DIR);
 }
 
 async function downloadPackages(config) {
-    for (let pkg of config.packages) {
-        const pkgDownloadPath = nodePath.join(REPO_DIR, pkg.name);
-        console.log(chalk.gray(`Downloading ${pkg.git}`))
-        await promisify(downloadGit)(pkg.git, pkgDownloadPath);
-        // Override the path
-        pkg.dir = pkgDownloadPath;
-    }
+  for (let pkg of config.packages) {
+    const pkgDownloadPath = nodePath.join(REPO_DIR, pkg.name);
+    console.log(chalk.gray(`Downloading ${pkg.git}`));
+    await promisify(downloadGit)(pkg.git, pkgDownloadPath);
+    // Override the path
+    pkg.dir = pkgDownloadPath;
+  }
 }
 
 async function installPackages(config) {
-
-    const publishedPackages = {};
-
-    function checkFolder(pkg) {
-        const dir = pkg.dir;
-        if (!fs.existsSync(dir)) {
-            console.warn(chalk.yellow(`${dir} not exists. Please update it in e2e/config.js.`));
-            return false;
-        }
-        if (!nodePath.isAbsolute(dir)) {
-            console.warn(chalk.yellow(`${dir} is not an absolute path. Please update it in e2e/config.js.`));
-            return false;
-        }
-        return true;
+  const publishedPackages = {};
+
+  function checkFolder(pkg) {
+    const dir = pkg.dir;
+    if (!fs.existsSync(dir)) {
+      console.warn(
+        chalk.yellow(`${dir} not exists. Please update it in e2e/config.js.`)
+      );
+      return false;
     }
+    if (!nodePath.isAbsolute(dir)) {
+      console.warn(
+        chalk.yellow(
+          `${dir} is not an absolute path. Please update it in e2e/config.js.`
+        )
+      );
+      return false;
+    }
+    return true;
+  }
 
-    function publishPackage(pkg) {
-        console.log(chalk.gray(`Publishing ${pkg.dir}`))
-
-        shell.cd(pkg.dir);
-
-        const packageJson = JSON.parse(fs.readFileSync(nodePath.join(pkg.dir, 'package.json')));
-        const tgzFileName = `${packageJson.name}-${packageJson.version}.tgz`;
-        const targetTgzFilePath = nodePath.join(PACKAGE_DIR, tgzFileName);
-
-        if (packageJson.dependencies) {
-            for (let depPkgName in packageJson.dependencies) {
-                const depPkg = config.packages.find(a => a.name === depPkgName);
-                if (depPkg && !publishedPackages[depPkgName]) {
-                    publishPackage(depPkg);
-                    // Come back.
-                    shell.cd(pkg.dir);
-                }
+  function publishPackage(pkg) {
+    console.log(chalk.gray(`Publishing ${pkg.dir}`));
 
-                shell.exec(`npm install`);
+    shell.cd(pkg.dir);
 
-                if (depPkg) {
-                    console.log(chalk.gray(`Installing dependency ${depPkgName} from "${publishedPackages[depPkgName]}" ...`));
-                    shell.exec(`npm install ${publishedPackages[depPkgName]} --no-save`);
-                    console.log(chalk.gray(`Install dependency ${depPkgName} done.`));
-                }
-            }
+    const packageJson = JSON.parse(
+      fs.readFileSync(nodePath.join(pkg.dir, 'package.json'))
+    );
+    const tgzFileName = `${packageJson.name}-${packageJson.version}.tgz`;
+    const targetTgzFilePath = nodePath.join(PACKAGE_DIR, tgzFileName);
+
+    if (packageJson.dependencies) {
+      for (let depPkgName in packageJson.dependencies) {
+        const depPkg = config.packages.find((a) => a.name === depPkgName);
+        if (depPkg && !publishedPackages[depPkgName]) {
+          publishPackage(depPkg);
+          // Come back.
+          shell.cd(pkg.dir);
         }
 
-        shell.exec(`npm pack`);
-        fs.renameSync(nodePath.join(pkg.dir, tgzFileName), targetTgzFilePath);
-        publishedPackages[packageJson.name] = targetTgzFilePath;
-    }
+        shell.exec(`npm install`);
 
-    for (let pkg of config.packages) {
-        if (!checkFolder(pkg)) {
-            return;
+        if (depPkg) {
+          console.log(
+            chalk.gray(
+              `Installing dependency ${depPkgName} from "${publishedPackages[depPkgName]}" ...`
+            )
+          );
+          shell.exec(`npm install ${publishedPackages[depPkgName]} --no-save`);
+          console.log(chalk.gray(`Install dependency ${depPkgName} done.`));
         }
-
-        publishPackage(pkg);
-    };
-
-    shell.cd(__dirname);
-    for (let pkg of config.packages) {
-        console.log(chalk.gray(`Installing ${pkg.name} from "${publishedPackages[pkg.name]}" ...`));
-        shell.exec(`npm install ${publishedPackages[pkg.name]} --no-save`);
-        console.log(chalk.gray(`Install ${pkg.name} done.`));
-    }
-
-    // Come back.
-    shell.cd(process.cwd());
-}
-
-async function buildRunCode() {
-    const files = await globby([
-        `${EXAMPLE_DIR}/data/option/*.json`,
-        `${EXAMPLE_DIR}/data-gl/option/*.json`
-    ]);
-
-    if (!files.length) {
-        throw new Error('You need to run `node tool/build-example.js` before run this test.');
+      }
     }
 
-    async function addTestCase(testName, testCode, deps, checkTs, extraImports, extraRequire) {
-        const ROOT_PATH = `${baseUrl}/public`;
+    shell.exec(`npm pack`);
+    fs.renameSync(nodePath.join(pkg.dir, tgzFileName), targetTgzFilePath);
+    publishedPackages[packageJson.name] = targetTgzFilePath;
+  }
 
-        const fullCode = buildExampleCode(buildPrepareCode(true) + testCode, deps, {
-            minimal: false,
-            ts: checkTs,
-            // Check if theme will break the minimal imports.
-            theme: TEST_THEME,
-            ROOT_PATH,
-            extraImports
-        });
-        const minimalCode = buildExampleCode(buildPrepareCode(true) + testCode, deps, {
-            minimal: true,
-            ts: checkTs,
-            theme: TEST_THEME,
-            ROOT_PATH,
-            extraImports
-        });
-        const legacyCode = buildExampleCode(buildPrepareCode(false) + testCode, deps, {
-            minimal: true,
-            esm: false,
-            ts: false,
-            theme: TEST_THEME,
-            ROOT_PATH,
-            extraImports: extraRequire
-        });
-
-        await fse.writeFile(
-            nodePath.join(RUN_CODE_DIR, testName + (checkTs ? '.ts' : '.js')),
-            prettier.format(fullCode, {
-                singleQuote: true,
-                parser: checkTs ? 'typescript' : 'babel'
-            }), 'utf-8'
-        );
-        await fse.writeFile(
-            nodePath.join(RUN_CODE_DIR, testName + `.${MINIMAL_POSTFIX}.${checkTs ? 'ts' : 'js'}`),
-            prettier.format(minimalCode, {
-                singleQuote: true,
-                parser: checkTs ? 'typescript' : 'babel'
-            }), 'utf-8'
-        );
-        await fse.writeFile(
-            nodePath.join(RUN_CODE_DIR, testName + `.${MINIMAL_LEGACY_POSTFIX}.js`),
-            prettier.format(legacyCode, {
-                singleQuote: true,
-                parser: 'babel'
-            }), 'utf-8'
-        );
-        console.log(
-            chalk.green('Generated: ', testName)
-        );
+  for (let pkg of config.packages) {
+    if (!checkFolder(pkg)) {
+      return;
     }
 
-    const builtinTestCases = await runTasks(files, async (fileName) => {
-        const isGL = fileName.startsWith(`${EXAMPLE_DIR}/data-gl`);
-        const testName = nodePath.basename(fileName, '.json');
-
-        if (testsPattern && !testsPattern.some(pattern => minimatch(testName, pattern))) {
-            return;
-        }
+    publishPackage(pkg);
+  }
 
-        const optionCode = await fse.readFile(fileName, 'utf-8');
-        let option;
-        try {
-            option = JSON.parse(optionCode);
-        }
-        catch (err) {
-            console.error(`Parse JSON error: fileName: ${fileName} | fileContent: ${optionCode}`);
-            throw err;
-        }
-        const testCode = await fse.readFile(nodePath.join(
-            EXAMPLE_DIR, isGL ? 'data-gl' : 'data', testName + '.js'
-        ), 'utf-8');
-
-        // TODO Ignore case with extension.
-
-        const deps = collectDeps(option).concat([
-            // TODO SVG
-            'CanvasRenderer'
-        ]);
-
-        if (
-            !(testName === 'map-HK' || testName === 'map-usa')  // Only test these two map examples
-            && (deps.includes('MapChart')
-                || deps.includes('GeoComponent')
-                || option.bmap)
-        ) {
-            console.warn(chalk.yellow(`Ignored map tests ${testName}`));
-            return;
-        }
+  shell.cd(__dirname);
+  for (let pkg of config.packages) {
+    console.log(
+      chalk.gray(
+        `Installing ${pkg.name} from "${publishedPackages[pkg.name]}" ...`
+      )
+    );
+    shell.exec(`npm install ${publishedPackages[pkg.name]} --no-save`);
+    console.log(chalk.gray(`Install ${pkg.name} done.`));
+  }
 
-        // TODO: Don't support TypeScript in GL
-        await addTestCase(testName, testCode, deps, !isGL);
+  // Come back.
+  shell.cd(process.cwd());
+}
 
-        return testName;
-    }, 20);
-    const extensionTestCases = await runTasks(await globby(__dirname + '/cases/*.js'), async (fileName) => {
-        const testName = nodePath.basename(fileName, '.js');
-        if (testsPattern && !testsPattern.some(pattern => minimatch(testName, pattern))) {
-            return;
-        }
+async function buildRunCode() {
+  const files = await globby([
+    `${EXAMPLE_DIR}/data/option/*.json`,
+    `${EXAMPLE_DIR}/data-gl/option/*.json`
+  ]);
+
+  if (!files.length) {
+    throw new Error(
+      'You need to run `node tool/build-example.js` before run this test.'
+    );
+  }
+
+  async function addTestCase(
+    testName,
+    testCode,
+    deps,
+    checkTs,
+    extraImports,
+    extraRequire
+  ) {
+    const ROOT_PATH = `${baseUrl}/public`;
+
+    const fullCode = buildExampleCode(buildPrepareCode(true) + testCode, deps, {
+      minimal: false,
+      ts: checkTs,
+      // Check if theme will break the minimal imports.
+      theme: TEST_THEME,
+      ROOT_PATH,
+      extraImports
+    });
+    const minimalCode = buildExampleCode(
+      buildPrepareCode(true) + testCode,
+      deps,
+      {
+        minimal: true,
+        ts: checkTs,
+        theme: TEST_THEME,
+        ROOT_PATH,
+        extraImports
+      }
+    );
+    const legacyCode = buildExampleCode(
+      buildPrepareCode(false) + testCode,
+      deps,
+      {
+        minimal: true,
+        esm: false,
+        ts: false,
+        theme: TEST_THEME,
+        ROOT_PATH,
+        extraImports: extraRequire
+      }
+    );
 
-        const testCode = await fse.readFile(fileName, 'utf-8');
-        let importsCode = '';
-        let requireCode = '';
-        try {
-            const fmResult = matter(testCode, {
-                delimiters: ['/*', '*/']
-            });
-            const extension = fmResult.data.extension;
-            if (extension) {
-                importsCode = `import '${extension}';`;
-                requireCode = `require('${extension}');`;
-            }
-        }
-        catch (e)  {
+    await fse.writeFile(
+      nodePath.join(RUN_CODE_DIR, testName + (checkTs ? '.ts' : '.js')),
+      prettier.format(fullCode, {
+        singleQuote: true,
+        parser: checkTs ? 'typescript' : 'babel'
+      }),
+      'utf-8'
+    );
+    await fse.writeFile(
+      nodePath.join(
+        RUN_CODE_DIR,
+        testName + `.${MINIMAL_POSTFIX}.${checkTs ? 'ts' : 'js'}`
+      ),
+      prettier.format(minimalCode, {
+        singleQuote: true,
+        parser: checkTs ? 'typescript' : 'babel'
+      }),
+      'utf-8'
+    );
+    await fse.writeFile(
+      nodePath.join(RUN_CODE_DIR, testName + `.${MINIMAL_LEGACY_POSTFIX}.js`),
+      prettier.format(legacyCode, {
+        singleQuote: true,
+        parser: 'babel'
+      }),
+      'utf-8'
+    );
+    console.log(chalk.green('Generated: ', testName));
+  }
+
+  const builtinTestCases = await runTasks(
+    files,
+    async (fileName) => {
+      const isGL = fileName.startsWith(`${EXAMPLE_DIR}/data-gl`);
+      const testName = nodePath.basename(fileName, '.json');
+
+      if (
+        testsPattern &&
+        !testsPattern.some((pattern) => minimatch(testName, pattern))
+      ) {
+        return;
+      }
+
+      const optionCode = await fse.readFile(fileName, 'utf-8');
+      let option;
+      try {
+        option = JSON.parse(optionCode);
+      } catch (err) {
+        console.error(
+          `Parse JSON error: fileName: ${fileName} | fileContent: ${optionCode}`
+        );
+        throw err;
+      }
+      const testCode = await fse.readFile(
+        nodePath.join(EXAMPLE_DIR, isGL ? 'data-gl' : 'data', testName + '.js'),
+        'utf-8'
+      );
+
+      // TODO Ignore case with extension.
+
+      const deps = collectDeps(option).concat([
+        // TODO SVG
+        'CanvasRenderer'
+      ]);
+
+      if (
+        !(testName === 'map-HK' || testName === 'map-usa') && // Only test these two map examples
+        (deps.includes('MapChart') ||
+          deps.includes('GeoComponent') ||
+          option.bmap)
+      ) {
+        console.warn(chalk.yellow(`Ignored map tests ${testName}`));
+        return;
+      }
+
+      // TODO: Don't support TypeScript in GL
+      await addTestCase(testName, testCode, deps, !isGL);
+
+      return testName;
+    },
+    20
+  );
+  const extensionTestCases = await runTasks(
+    await globby(__dirname + '/cases/*.js'),
+    async (fileName) => {
+      const testName = nodePath.basename(fileName, '.js');
+      if (
+        testsPattern &&
+        !testsPattern.some((pattern) => minimatch(testName, pattern))
+      ) {
+        return;
+      }
+
+      const testCode = await fse.readFile(fileName, 'utf-8');
+      let importsCode = '';
+      let requireCode = '';
+      try {
+        const fmResult = matter(testCode, {
+          delimiters: ['/*', '*/']
+        });
+        const extension = fmResult.data.extension;
+        if (extension) {
+          importsCode = `import '${extension}';`;
+          requireCode = `require('${extension}');`;
         }
-        await addTestCase(testName, testCode, [], false, importsCode, requireCode);
-
-        return testName;
-    }, 20);
-
-    return builtinTestCases.concat(extensionTestCases).filter(a => !!a);
+      } catch (e) {}
+      await addTestCase(
+        testName,
+        testCode,
+        [],
+        false,
+        importsCode,
+        requireCode
+      );
+
+      return testName;
+    },
+    20
+  );
+
+  return builtinTestCases.concat(extensionTestCases).filter((a) => !!a);
 }
 
 async function compileTs(tsTestFiles, result) {
-    const config = JSON.parse(fs.readFileSync(nodePath.join(__dirname, 'tsconfig.json'), 'utf-8'));
-
-    const compilerOptions = {
-        ...config.compilerOptions
-    };
-
-    const {options, errors} = ts.convertCompilerOptionsFromJson(compilerOptions, nodePath.resolve(__dirname));
-
-    if (errors.length) {
-        let errMsg = 'tsconfig parse failed: '
-            + errors.map(error => error.messageText).join('. ')
-            + '\n compilerOptions: \n' + JSON.stringify(config.compilerOptions, null, 4);
-        assert(false, errMsg);
-    }
-
-    // Generate this config file for checking the source code in vscode.
-    fs.writeFileSync(nodePath.join(RUN_CODE_DIR, 'tsconfig.json'), JSON.stringify({
+  const config = JSON.parse(
+    fs.readFileSync(nodePath.join(__dirname, 'tsconfig.json'), 'utf-8')
+  );
+
+  const compilerOptions = {
+    ...config.compilerOptions
+  };
+
+  const { options, errors } = ts.convertCompilerOptionsFromJson(
+    compilerOptions,
+    nodePath.resolve(__dirname)
+  );
+
+  if (errors.length) {
+    let errMsg =
+      'tsconfig parse failed: ' +
+      errors.map((error) => error.messageText).join('. ') +
+      '\n compilerOptions: \n' +
+      JSON.stringify(config.compilerOptions, null, 4);
+    assert(false, errMsg);
+  }
+
+  // Generate this config file for checking the source code in vscode.
+  fs.writeFileSync(
+    nodePath.join(RUN_CODE_DIR, 'tsconfig.json'),
+    JSON.stringify(
+      {
         compilerOptions
-    }, null, 2), 'utf-8');
-
-    // See: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
-    let program = ts.createProgram(tsTestFiles, options);
-    let emitResult = program.emit();
-
-    let allDiagnostics = ts
-        .getPreEmitDiagnostics(program)
-        .concat(emitResult.diagnostics);
-
-    allDiagnostics.forEach(diagnostic => {
-        if (diagnostic.file) {
-            let {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
-            let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
-
-            const compilerError = {
-                location: [line + 1, character + 1],
-                message
-            };
-            if (diagnostic.file.fileName.endsWith(`${MINIMAL_POSTFIX}.ts`)) {
-                const basename = nodePath.basename(diagnostic.file.fileName, `.${MINIMAL_POSTFIX}.ts`);
-                if (!result[basename]) {
-                    throw new Error(`${basename} does not exists in result.`);
-                }
-                result[basename].compileErrors.minimal.push(compilerError);
-            }
-            else {
-                const basename = nodePath.basename(diagnostic.file.fileName, `.ts`);
-                if (!result[basename]) {
-                    throw new Error(`${basename} does not exists in result.`);
-                }
-                result[basename].compileErrors.full.push(compilerError);
-            }
-            // console.log(chalk.red(`${diagnostic.file.fileName} (${line + 1},${character + 1})`));
-            // console.log(chalk.gray(message));
+      },
+      null,
+      2
+    ),
+    'utf-8'
+  );
+
+  // See: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API
+  let program = ts.createProgram(tsTestFiles, options);
+  let emitResult = program.emit();
+
+  let allDiagnostics = ts
+    .getPreEmitDiagnostics(program)
+    .concat(emitResult.diagnostics);
+
+  allDiagnostics.forEach((diagnostic) => {
+    if (diagnostic.file) {
+      let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
+        diagnostic.start
+      );
+      let message = ts.flattenDiagnosticMessageText(
+        diagnostic.messageText,
+        '\n'
+      );
+
+      const compilerError = {
+        location: [line + 1, character + 1],
+        message
+      };
+      if (diagnostic.file.fileName.endsWith(`${MINIMAL_POSTFIX}.ts`)) {
+        const basename = nodePath.basename(
+          diagnostic.file.fileName,
+          `.${MINIMAL_POSTFIX}.ts`
+        );
+        if (!result[basename]) {
+          throw new Error(`${basename} does not exists in result.`);
         }
-        else {
-            console.log(chalk.red(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')));
+        result[basename].compileErrors.minimal.push(compilerError);
+      } else {
+        const basename = nodePath.basename(diagnostic.file.fileName, `.ts`);
+        if (!result[basename]) {
+          throw new Error(`${basename} does not exists in result.`);
         }
-    });
-    // assert(!emitResult.emitSkipped, 'ts compile failed.');
+        result[basename].compileErrors.full.push(compilerError);
+      }
+      // console.log(chalk.red(`${diagnostic.file.fileName} (${line + 1},${character + 1})`));
+      // console.log(chalk.gray(message));
+    } else {
+      console.log(
+        chalk.red(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'))
+      );
+    }
+  });
+  // assert(!emitResult.emitSkipped, 'ts compile failed.');
 }
 
 async function webpackBundle(esbuildService, entry, result) {
-    return new Promise((resolve) => {
-        webpack({
-            entry,
-            output: {
-                path: BUNDLE_DIR,
-                filename: '[name].js'
-            },
-            // Use esbuild as minify, terser is tooooooo slow for so much tests.
-            optimization: {
-                minimizer: MINIFY_BUNDLE ? [{
-                    apply(compiler) {
-                        compiler.hooks.compilation.tap(
-                            'ESBuild Minify',
-                            (compilation) => {
-                                compilation.hooks.optimizeChunkAssets.tapPromise(
-                                    'ESBuild Minify',
-                                    async (chunks) => {
-                                        for (const chunk of chunks) {
-                                            for (const file of chunk.files) {
-                                                const asset = compilation.assets[file];
-                                                const { source } = asset.sourceAndMap();
-                                                const result = await esbuildService.transform(source, {
-                                                    minify: true,
-                                                    sourcemap: false
-                                                });
-                                                compilation.updateAsset(file, () => {
-                                                    return new RawSource(result.code || '');
-                                                });
-                                            }
-                                        }
-                                    }
+  return new Promise((resolve) => {
+    webpack(
+      {
+        entry,
+        output: {
+          path: BUNDLE_DIR,
+          filename: '[name].js'
+        },
+        // Use esbuild as minify, terser is tooooooo slow for so much tests.
+        optimization: {
+          minimizer: MINIFY_BUNDLE
+            ? [
+                {
+                  apply(compiler) {
+                    compiler.hooks.compilation.tap(
+                      'ESBuild Minify',
+                      (compilation) => {
+                        compilation.hooks.optimizeChunkAssets.tapPromise(
+                          'ESBuild Minify',
+                          async (chunks) => {
+                            for (const chunk of chunks) {
+                              for (const file of chunk.files) {
+                                const asset = compilation.assets[file];
+                                const { source } = asset.sourceAndMap();
+                                const result = await esbuildService.transform(
+                                  source,
+                                  {
+                                    minify: true,
+                                    sourcemap: false
+                                  }
                                 );
-                            },
-                          );
-
-                    }
-                }] : []
-            }
-        }, (err, stats) => {
-            if (err || stats.hasErrors()) {
-                if (err) {
-                    console.error(err.stack || err);
-                    if (err.details) {
-                        console.error(err.details);
-                    }
-                    resolve();
-                    return;
-                }
-
-                const info = stats.toJson();
-
-                if (stats.hasErrors()) {
-                    console.error(info.errors);
-                }
-
-                if (stats.hasWarnings()) {
-                    console.warn(info.warnings);
+                                compilation.updateAsset(file, () => {
+                                  return new RawSource(result.code || '');
+                                });
+                              }
+                            }
+                          }
+                        );
+                      }
+                    );
+                  }
                 }
+              ]
+            : []
+        }
+      },
+      (err, stats) => {
+        if (err || stats.hasErrors()) {
+          if (err) {
+            console.error(err.stack || err);
+            if (err.details) {
+              console.error(err.details);
             }
-            else {
-                console.log(chalk.green(`${Object.values(entry).map(a => `Bundled ${a}`).join('\n')}`));
-            }
-
             resolve();
-        });
-    })
+            return;
+          }
+
+          const info = stats.toJson();
+
+          if (stats.hasErrors()) {
+            console.error(info.errors);
+          }
+
+          if (stats.hasWarnings()) {
+            console.warn(info.warnings);
+          }
+        } else {
+          console.log(
+            chalk.green(
+              `${Object.values(entry)
+                .map((a) => `Bundled ${a}`)
+                .join('\n')}`
+            )
+          );
+        }
+
+        resolve();
+      }
+    );
+  });
 }
 
 function esbuildBundle(entry, result, minify) {
-    return esbuild.build({
-        entryPoints: entry,
-        bundle: true,
-        minify: minify,
-        define: {
-            'process.env.NODE_ENV': JSON.stringify(minify ? 'production' : 'development')
-        },
-        outdir: BUNDLE_DIR
-    });
+  return esbuild.build({
+    entryPoints: entry,
+    bundle: true,
+    minify: minify,
+    define: {
+      'process.env.NODE_ENV': JSON.stringify(
+        minify ? 'production' : 'development'
+      )
+    },
+    outdir: BUNDLE_DIR
+  });
 }
 
 async function bundle(entryFiles, result) {
-    if (USE_WEBPACK) {
-        // Split to multiple buckets to seepup bundle
-        // TODO Multiple entry may have effects on the final bundle.
-        const BUCKET_SIZE = 1;
-        const buckets = [];
-        const esbuildService = await esbuild.startService();
-        let count = 0;
-        outer: while (true) {
-            const bucket = {};
-            for (let i = 0; i < BUCKET_SIZE; i++) {
-                const filePath = entryFiles[count++];
-                if (!filePath) {
-                    break outer;
-                }
-                const basename = nodePath.basename(filePath, '.js');
-                bucket[basename] = filePath;
-            }
-            buckets.push(bucket);
-        }
-
-        // TODO Multiple thread.
-        for (let bucket of buckets) {
-            await webpackBundle(esbuildService, bucket, result);
+  if (USE_WEBPACK) {
+    // Split to multiple buckets to seepup bundle
+    // TODO Multiple entry may have effects on the final bundle.
+    const BUCKET_SIZE = 1;
+    const buckets = [];
+    const esbuildService = await esbuild.startService();
+    let count = 0;
+    outer: while (true) {
+      const bucket = {};
+      for (let i = 0; i < BUCKET_SIZE; i++) {
+        const filePath = entryFiles[count++];
+        if (!filePath) {
+          break outer;
         }
+        const basename = nodePath.basename(filePath, '.js');
+        bucket[basename] = filePath;
+      }
+      buckets.push(bucket);
+    }
 
-        esbuildService.stop();
+    // TODO Multiple thread.
+    for (let bucket of buckets) {
+      await webpackBundle(esbuildService, bucket, result);
     }
-    else {
-        for (let file of entryFiles) {
-            await esbuildBundle([file], result, MINIFY_BUNDLE);
-            console.log(chalk.green(`Bundled ${file}`));
-        }
+
+    esbuildService.stop();
+  } else {
+    for (let file of entryFiles) {
+      await esbuildBundle([file], result, MINIFY_BUNDLE);
+      console.log(chalk.green(`Bundled ${file}`));
     }
+  }
 }
 
 function waitTime(time) {
-    return new Promise((resolve) => setTimeout(resolve, time));
+  return new Promise((resolve) => setTimeout(resolve, time));
 }
 async function runExamples(jsFiles, result) {
-    const fileServer = new nStatic.Server(__dirname + '/../');
-    const server = require('http').createServer(function (request, response) {
-        request.addListener('end', function () {
-            fileServer.serve(request, response);
-        }).resume();
-    })
-    server.listen(port);
+  const fileServer = new nStatic.Server(__dirname + '/../');
+  const server = require('http').createServer(function (request, response) {
+    request
+      .addListener('end', function () {
+        fileServer.serve(request, response);
+      })
+      .resume();
+  });
+  server.listen(port);
+
+  try {
+    const IGNORE_LOG = [
+      'A cookie associated with a cross-site resource at',
+      'A parser-blocking, cross site',
+      // For ECharts GL
+      'RENDER WARNING',
+      'GL ERROR',
+      'GL_INVALID_OPERATION'
+    ];
+
+    const browser = await puppeteer.launch({
+      headless: false,
+      args: [
+        '--headless',
+        '--hide-scrollbars',
+        // https://github.com/puppeteer/puppeteer/issues/4913
+        '--use-gl=egl',
+        '--mute-audio'
+      ]
+    });
 
-    try {
-        const IGNORE_LOG = [
-            'A cookie associated with a cross-site resource at',
-            'A parser-blocking, cross site',
-            // For ECharts GL
-            'RENDER WARNING',
-            'GL ERROR',
-            'GL_INVALID_OPERATION'
-        ];
-
-        const browser = await puppeteer.launch({
-            headless: false,
-            args: [
-                '--headless',
-                '--hide-scrollbars',
-                // https://github.com/puppeteer/puppeteer/issues/4913
-                '--use-gl=egl',
-                '--mute-audio'
-            ]
+    await runTasks(
+      jsFiles,
+      async (file) => {
+        const page = await browser.newPage();
+        const basename = nodePath.basename(file, '.js');
+        await page.setViewport({ width: 800, height: 600 });
+
+        page.on('pageerror', function (err) {
+          // TODO Record pageerror
+          console.error(chalk.red(`[PAGE ERROR] [${basename}]`));
+          console.error(chalk.red(err.toString()));
+        });
+        page.on('console', (msg) => {
+          const text = msg.text();
+          if (!IGNORE_LOG.find((a) => text.indexOf(a) >= 0)) {
+            console.log(chalk.gray(`[PAGE LOG] [${basename}]: ${text}`));
+          }
         });
 
-        await runTasks(jsFiles, async (file) => {
-            const page = await browser.newPage();
-            const basename = nodePath.basename(file, '.js');
-            await page.setViewport({ width: 800, height: 600 });
-
-            page.on('pageerror', function (err) {
-                // TODO Record pageerror
-                console.error(chalk.red(`[PAGE ERROR] [${basename}]`));
-                console.error(chalk.red(err.toString()));
-            });
-            page.on('console', msg => {
-                const text = msg.text();
-                if (!IGNORE_LOG.find(a => text.indexOf(a) >= 0)) {
-                    console.log(chalk.gray(`[PAGE LOG] [${basename}]: ${text}`));
-                }
-            });
-
-            await page.goto(`${baseUrl}/e2e/template.html`, {
-                waitUntil: 'networkidle0',
-                timeout: 10000
-            });
-            await page.addScriptTag({
-                url: `${baseUrl}/e2e/tmp/bundles/${basename}.js`
-            });
-            await waitTime(200);
-
-            await page.screenshot({
-                type: 'png',
-                path: nodePath.resolve(SCREENSHOTS_DIR, basename + '.png')
-            });
-
-            await page.close();
-
-            console.log(chalk.green(`Rendered ${file}`));
-        }, 8);
-    }
-    catch (e) {
-        server.close();
-        throw e;
-    }
-}
+        await page.goto(`${baseUrl}/e2e/template.html`, {
+          waitUntil: 'networkidle0',
+          timeout: 10000
+        });
+        await page.addScriptTag({
+          url: `${baseUrl}/e2e/tmp/bundles/${basename}.js`
+        });
+        await waitTime(200);
 
+        await page.screenshot({
+          type: 'png',
+          path: nodePath.resolve(SCREENSHOTS_DIR, basename + '.png')
+        });
 
-async function compareExamples(testNames, result) {
+        await page.close();
 
-    function writePNG(png, diffPath) {
-        return new Promise(resolve => {
-            const writer = fs.createWriteStream(diffPath);
-            png.pack().pipe(writer);
-            writer.on('finish', () => { resolve(); });
-        });
-    }
+        console.log(chalk.green(`Rendered ${file}`));
+      },
+      8
+    );
+  } catch (e) {
+    server.close();
+    throw e;
+  }
+}
 
-    for (let testName of testNames) {
-        const diffMinimal = await compareImage(
-            nodePath.resolve(SCREENSHOTS_DIR, testName + '.png'),
-            nodePath.resolve(SCREENSHOTS_DIR, `${testName}.${MINIMAL_POSTFIX}.png`)
-        );
-        const diffMinimalLegacy = await compareImage(
-            nodePath.resolve(SCREENSHOTS_DIR, testName + '.png'),
-            nodePath.resolve(SCREENSHOTS_DIR, `${testName}.${MINIMAL_LEGACY_POSTFIX}.png`)
-        );
+async function compareExamples(testNames, result) {
+  function writePNG(png, diffPath) {
+    return new Promise((resolve) => {
+      const writer = fs.createWriteStream(diffPath);
+      png.pack().pipe(writer);
+      writer.on('finish', () => {
+        resolve();
+      });
+    });
+  }
 
-        const diffMinimalPNGPath = nodePath.resolve(SCREENSHOTS_DIR, `${testName}.${MINIMAL_POSTFIX}.diff.png`);
-        const diffMinimalLegacyPNGPath = nodePath.resolve(SCREENSHOTS_DIR, `${testName}.${MINIMAL_LEGACY_POSTFIX}.diff.png`);
+  for (let testName of testNames) {
+    const diffMinimal = await compareImage(
+      nodePath.resolve(SCREENSHOTS_DIR, testName + '.png'),
+      nodePath.resolve(SCREENSHOTS_DIR, `${testName}.${MINIMAL_POSTFIX}.png`)
+    );
+    const diffMinimalLegacy = await compareImage(
+      nodePath.resolve(SCREENSHOTS_DIR, testName + '.png'),
+      nodePath.resolve(
+        SCREENSHOTS_DIR,
+        `${testName}.${MINIMAL_LEGACY_POSTFIX}.png`
+      )
+    );
 
-        writePNG(diffMinimal.diffPNG, diffMinimalPNGPath);
-        writePNG(diffMinimalLegacy.diffPNG, diffMinimalLegacyPNGPath);
+    const diffMinimalPNGPath = nodePath.resolve(
+      SCREENSHOTS_DIR,
+      `${testName}.${MINIMAL_POSTFIX}.diff.png`
+    );
+    const diffMinimalLegacyPNGPath = nodePath.resolve(
+      SCREENSHOTS_DIR,
+      `${testName}.${MINIMAL_LEGACY_POSTFIX}.diff.png`
+    );
 
-        result[testName].screenshotDiff = {
-            minimal: {
-                ratio: diffMinimal.diffRatio,
-                png: nodePath.basename(diffMinimalPNGPath)
-            },
-            minimalLegacy: {
-                ratio: diffMinimalLegacy.diffRatio,
-                png: nodePath.basename(diffMinimalLegacyPNGPath)
-            }
-        }
+    writePNG(diffMinimal.diffPNG, diffMinimalPNGPath);
+    writePNG(diffMinimalLegacy.diffPNG, diffMinimalLegacyPNGPath);
+
+    result[testName].screenshotDiff = {
+      minimal: {
+        ratio: diffMinimal.diffRatio,
+        png: nodePath.basename(diffMinimalPNGPath)
+      },
+      minimalLegacy: {
+        ratio: diffMinimalLegacy.diffRatio,
+        png: nodePath.basename(diffMinimalLegacyPNGPath)
+      }
+    };
 
-        if (diffMinimal.diffRatio > 0 || diffMinimalLegacy.diffRatio > 0) {
-            console.log(chalk.red(`Failed ${testName}`));
-        }
-        else {
-            console.log(chalk.green(`Passed ${testName}`));
-        }
+    if (diffMinimal.diffRatio > 0 || diffMinimalLegacy.diffRatio > 0) {
+      console.log(chalk.red(`Failed ${testName}`));
+    } else {
+      console.log(chalk.green(`Passed ${testName}`));
     }
+  }
 }
 
-
 async function main() {
-    let result;
-
-    if (!args.skip && !args.tests) {
-        // Don't clean up if skipping some of the stages.
-        await prepare();
-        result = {};
-    }
-    else {
-        // Read result.
-        try {
-            result = JSON.parse(fs.readFileSync(__dirname + '/tmp/result.json', 'utf-8'));
-        }
-        catch (e) {
-            console.error(e);
-            throw 'Must run full e2e test without --skip and --tests at least once.';
-        }
-    }
-
-    function isNotSkipped(stage) {
-        return !((args.skip || '').indexOf(stage) >= 0);
+  let result;
+
+  if (!args.skip && !args.tests) {
+    // Don't clean up if skipping some of the stages.
+    await prepare();
+    result = {};
+  } else {
+    // Read result.
+    try {
+      result = JSON.parse(
+        fs.readFileSync(__dirname + '/tmp/result.json', 'utf-8')
+      );
+    } catch (e) {
+      console.error(e);
+      throw 'Must run full e2e test without --skip and --tests at least once.';
     }
+  }
 
-    // We don't have to test the npm if bundle is also skipped.
-    if (isNotSkipped('npm') && isNotSkipped('bundle')) {
-        if (!args.local) {
-            console.log(chalk.gray('Downloading packages'));
-            await downloadPackages(config);
-        }
+  function isNotSkipped(stage) {
+    return !((args.skip || '').indexOf(stage) >= 0);
+  }
 
-        console.log(chalk.gray('Installing packages'));
-        await installPackages(config);
-    }
-    else {
-        console.log(chalk.yellow('Skipped NPM.'));
+  // We don't have to test the npm if bundle is also skipped.
+  if (isNotSkipped('npm') && isNotSkipped('bundle')) {
+    if (!args.local) {
+      console.log(chalk.gray('Downloading packages'));
+      await downloadPackages(config);
     }
 
-
-    console.log(chalk.gray('Generating codes'));
-    // Always build code.
-    const testNames = await buildRunCode();
-
-    for (let key of testNames) {
-        result[key] = result[key] || {};
-    }
-
-    Object.keys(result).forEach(key => {
-        // Always do TS check on all tests. So reset all compile errors.
-        result[key].compileErrors = {
-            full: [],
-            minimal: [],
-            minimalLegacy: []
-        };
-    });
-
-    console.log('Compiling TypeScript');
-    // Always run typescript check to generate the js code.
-    await compileTs(
-        (await globby(nodePath.join(RUN_CODE_DIR, '*.ts')))
-            // No need to check types of the minimal legacy imports
-            .filter(a => !a.endsWith(`${MINIMAL_LEGACY_POSTFIX}.ts`)),
-        result
+    console.log(chalk.gray('Installing packages'));
+    await installPackages(config);
+  } else {
+    console.log(chalk.yellow('Skipped NPM.'));
+  }
+
+  console.log(chalk.gray('Generating codes'));
+  // Always build code.
+  const testNames = await buildRunCode();
+
+  for (let key of testNames) {
+    result[key] = result[key] || {};
+  }
+
+  Object.keys(result).forEach((key) => {
+    // Always do TS check on all tests. So reset all compile errors.
+    result[key].compileErrors = {
+      full: [],
+      minimal: [],
+      minimalLegacy: []
+    };
+  });
+
+  console.log('Compiling TypeScript');
+  // Always run typescript check to generate the js code.
+  await compileTs(
+    (
+      await globby(nodePath.join(RUN_CODE_DIR, '*.ts'))
+    )
+      // No need to check types of the minimal legacy imports
+      .filter((a) => !a.endsWith(`${MINIMAL_LEGACY_POSTFIX}.ts`)),
+    result
+  );
+
+  if (isNotSkipped('bundle')) {
+    console.log(
+      chalk.green(`Bundling with ${USE_WEBPACK ? 'webpack' : 'esbuild'}`)
     );
-
-    if (isNotSkipped('bundle')) {
-        console.log(chalk.green(`Bundling with ${USE_WEBPACK ? 'webpack' : 'esbuild'}`));
-        const jsFiles = [];
-        for (let testName of testNames) {
-            jsFiles.push(
-                nodePath.join(RUN_CODE_DIR, `${testName}.js`),
-                nodePath.join(RUN_CODE_DIR, `${testName}.${MINIMAL_POSTFIX}.js`),
-                nodePath.join(RUN_CODE_DIR, `${testName}.${MINIMAL_LEGACY_POSTFIX}.js`)
-            );
-        }
-        await bundle(jsFiles, result);
-    }
-    else {
-        console.log(chalk.yellow('Skipped Bundle.'));
-    }
-
-    if (isNotSkipped('render')) {
-        console.log(chalk.green('Running examples'));
-        const bundleFiles = [];
-        for (let testName of testNames) {
-            bundleFiles.push(
-                nodePath.join(BUNDLE_DIR, `${testName}.js`),
-                nodePath.join(BUNDLE_DIR, `${testName}.${MINIMAL_POSTFIX}.js`),
-                nodePath.join(BUNDLE_DIR, `${testName}.${MINIMAL_LEGACY_POSTFIX}.js`)
-            );
-        }
-        await runExamples(bundleFiles, result);
-    }
-    else {
-        console.log(chalk.yellow('Skipped Render.'));
-    }
-
-    if (isNotSkipped('compare')) {
-        console.log(chalk.green('Comparing Results'));
-        await compareExamples(testNames, result);
+    const jsFiles = [];
+    for (let testName of testNames) {
+      jsFiles.push(
+        nodePath.join(RUN_CODE_DIR, `${testName}.js`),
+        nodePath.join(RUN_CODE_DIR, `${testName}.${MINIMAL_POSTFIX}.js`),
+        nodePath.join(RUN_CODE_DIR, `${testName}.${MINIMAL_LEGACY_POSTFIX}.js`)
+      );
     }
-    else {
-        console.log(chalk.yellow('Skipped Compare.'));
+    await bundle(jsFiles, result);
+  } else {
+    console.log(chalk.yellow('Skipped Bundle.'));
+  }
+
+  if (isNotSkipped('render')) {
+    console.log(chalk.green('Running examples'));
+    const bundleFiles = [];
+    for (let testName of testNames) {
+      bundleFiles.push(
+        nodePath.join(BUNDLE_DIR, `${testName}.js`),
+        nodePath.join(BUNDLE_DIR, `${testName}.${MINIMAL_POSTFIX}.js`),
+        nodePath.join(BUNDLE_DIR, `${testName}.${MINIMAL_LEGACY_POSTFIX}.js`)
+      );
     }
-
-    fs.writeFileSync(__dirname + '/tmp/result.json', JSON.stringify(
-        result, null, 2
-    ), 'utf-8');
+    await runExamples(bundleFiles, result);
+  } else {
+    console.log(chalk.yellow('Skipped Render.'));
+  }
+
+  if (isNotSkipped('compare')) {
+    console.log(chalk.green('Comparing Results'));
+    await compareExamples(testNames, result);
+  } else {
+    console.log(chalk.yellow('Skipped Compare.'));
+  }
+
+  fs.writeFileSync(
+    __dirname + '/tmp/result.json',
+    JSON.stringify(result, null, 2),
+    'utf-8'
+  );
 }
 
-main().catch(e => {
+main()
+  .catch((e) => {
     console.error(e);
     process.exit();
-}).then(() => {
+  })
+  .then(() => {
     process.exit();
-});
+  });
 
-process.on('SIGINT', function() {
-    console.log('Closing');
-    // Close through ctrl + c;
-    process.exit();
-});
\ No newline at end of file
+process.on('SIGINT', function () {
+  console.log('Closing');
+  // Close through ctrl + c;
+  process.exit();
+});
diff --git a/e2e/package.json b/e2e/package.json
index bb44880..5dbec9c 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -9,6 +9,5 @@
   },
   "author": "",
   "license": "ISC",
-  "dependencies": {
-  }
+  "dependencies": {}
 }
diff --git a/e2e/preview.html b/e2e/preview.html
index fb94486..46f4ad5 100644
--- a/e2e/preview.html
+++ b/e2e/preview.html
@@ -1,27 +1,34 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@2.2.4/dist/jquery.min.js"></script>
-    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dat.gui@0.6.5/build/dat.gui.min.js"></script>
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <script
+      type="text/javascript"
+      src="https://cdn.jsdelivr.net/npm/jquery@2.2.4/dist/jquery.min.js"
+    ></script>
+    <script
+      type="text/javascript"
+      src="https://cdn.jsdelivr.net/npm/dat.gui@0.6.5/build/dat.gui.min.js"
+    ></script>
     <title>Test Container</title>
-</head>
-<body>
+  </head>
+  <body>
     <style>
-        html, body {
-            margin: 0;
-            overflow: hidden;
-        }
-        #main {
-            position: absolute;
-            left: 0px;
-            top: 0px;
-            right: 0px;
-            bottom: 0px;
-        }
+      html,
+      body {
+        margin: 0;
+        overflow: hidden;
+      }
+      #main {
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        right: 0px;
+        bottom: 0px;
+      }
     </style>
     <div id="main"></div>
     <script src="tmp/bundles/graph-grid.js"></script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/e2e/report.html b/e2e/report.html
index d4c44f2..edc4589 100644
--- a/e2e/report.html
+++ b/e2e/report.html
@@ -1,186 +1,209 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Test Report</title>
-    <link href="https://cdn.jsdelivr.net/npm/element-ui@2.14.1/lib/theme-chalk/index.css" rel="stylesheet" type="text/css">
+    <link
+      href="https://cdn.jsdelivr.net/npm/element-ui@2.14.1/lib/theme-chalk/index.css"
+      rel="stylesheet"
+      type="text/css"
+    />
 
     <style>
-        #app {
-            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
-        }
+      #app {
+        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+      }
 
-        .import-type-title {
-            padding: 10px;
-        }
+      .import-type-title {
+        padding: 10px;
+      }
 
-        .import-type-title div {
-            font-size: 14px;
-            font-family:'Courier New', Courier, monospace;
-        }
+      .import-type-title div {
+        font-size: 14px;
+        font-family: 'Courier New', Courier, monospace;
+      }
 
-        .type-error-log {
-            padding: 10px;
-            color: red;
-            font-family:'Courier New', Courier, monospace;
-        }
-        .type-error-log pre {
-            margin: 5px 0;
-            padding: 0;
-            /* https://css-tricks.com/snippets/css/make-pre-text-wrap/ */
-            white-space: pre-wrap;
-        }
-        .type-passed {
-            font-size: 14px;
-            color: green;
-            font-family:'Courier New', Courier, monospace;
-            padding: 10px;
-        }
+      .type-error-log {
+        padding: 10px;
+        color: red;
+        font-family: 'Courier New', Courier, monospace;
+      }
+      .type-error-log pre {
+        margin: 5px 0;
+        padding: 0;
+        /* https://css-tricks.com/snippets/css/make-pre-text-wrap/ */
+        white-space: pre-wrap;
+      }
+      .type-passed {
+        font-size: 14px;
+        color: green;
+        font-family: 'Courier New', Courier, monospace;
+        padding: 10px;
+      }
     </style>
-
-</head>
-<body>
+  </head>
+  <body>
     <div id="app">
-        <el-tabs v-model="tab" type="border-card">
-            <el-tab-pane label="Type Checking" name="typechecking">
-                <div v-for="item in typeCheckingResult">
-                    <h3>{{ item.testName }}({{item.compileErrorsCount}})</h3>
-                    <el-row :gutter="30">
-                        <el-col :span="12">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <div class="import-type-title">
-                                    <div>全量引入</div>
-                                    <div>Full Import</div>
-                                </div>
-                                <div class="type-error-log" v-if="item.compileErrors.full.length">
-                                    <div v-for="compileError in item.compileErrors.full">
-                                        <pre>[{{compileError.location.join(', ')}}] {{compileError.message}}</pre>
-                                    </div>
-                                </div>
-                                <div class="type-passed" v-else>
-                                    No Error
-                                </div>
-                            </el-card>
-                        </el-col>
-                        <el-col :span="12">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <div class="import-type-title">
-                                    <div>按需引入</div>
-                                    <div>Minimal Import </div>
-                                </div>
-                                <div class="type-error-log" v-if="item.compileErrors.minimal.length">
-                                    <div v-for="compileError in item.compileErrors.minimal">
-                                        <pre>[{{compileError.location.join(', ')}}] {{compileError.message}}</pre>
-                                    </div>
-                                </div>
-                                <div class="type-passed" v-else>
-                                    No Error
-                                </div>
-                            </el-card>
-                        </el-col>
-                    </el-row>
-                </div>
-            </el-tab-pane>
-            <el-tab-pane label="Screenshots Compare" name="screenshots">
-                <div v-for="item in screenshotsCompareResult">
-                    <h3>{{ item.testName }}({{item.screenshotDiffRatio}})</h3>
-                    <el-row :gutter="10">
-                        <el-col :span="4">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <el-image :src="SCREENSHOT_ROOT + item.testName + '.png'"></el-image>
-                                <div class="import-type-title">
-                                    <div>全量引入</div>
-                                    <div>Full Import</div>
-                                </div>
-                            </el-card>
-                        </el-col>
-                        <el-col :span="4">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <el-image :src="SCREENSHOT_ROOT + item.testName + '.minimal.png'"></el-image>
-                                <div class="import-type-title">
-                                    <div>按需引入</div>
-                                    <div>Minimal Import </div>
-                                </div>
-                            </el-card>
-                        </el-col>
-                        <el-col :span="4">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <el-image :src="SCREENSHOT_ROOT + item.testName + '.minimal.legacy.png'"></el-image>
-                                <div class="import-type-title">
-                                    <div>自注册方式按需引入</div>
-                                    <div>Minimal Import with Self Registion</div>
-                                </div>
-                            </el-card>
-                        </el-col>
-                        <el-col :span="4">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <el-image :src="SCREENSHOT_ROOT + item.testName + '.minimal.diff.png'"></el-image>
-                                <div class="import-type-title">
-                                    <div>按需引入差异</div>
-                                    <div>Diff of Minimal Import </div>
-                                </div>
-                            </el-card>
-                        </el-col>
-                        <el-col :span="4">
-                            <el-card :body-style="{ padding: '0px' }">
-                                <el-image :src="SCREENSHOT_ROOT + item.testName + '.minimal.legacy.diff.png'"></el-image>
-                                <div class="import-type-title">
-                                    <div>自注册方式按需引入差异</div>
-                                    <div>Diff of Minimal Import with Self Registion</div>
-                                </div>
-                            </el-card>
-                        </el-col>
-                    </el-row>
-                </div>
-            </el-tab-pane>
-        </el-tabs>
+      <el-tabs v-model="tab" type="border-card">
+        <el-tab-pane label="Type Checking" name="typechecking">
+          <div v-for="item in typeCheckingResult">
+            <h3>{{ item.testName }}({{item.compileErrorsCount}})</h3>
+            <el-row :gutter="30">
+              <el-col :span="12">
+                <el-card :body-style="{ padding: '0px' }">
+                  <div class="import-type-title">
+                    <div>全量引入</div>
+                    <div>Full Import</div>
+                  </div>
+                  <div
+                    class="type-error-log"
+                    v-if="item.compileErrors.full.length"
+                  >
+                    <div v-for="compileError in item.compileErrors.full">
+                      <pre>
+[{{compileError.location.join(', ')}}] {{compileError.message}}</pre
+                      >
+                    </div>
+                  </div>
+                  <div class="type-passed" v-else>No Error</div>
+                </el-card>
+              </el-col>
+              <el-col :span="12">
+                <el-card :body-style="{ padding: '0px' }">
+                  <div class="import-type-title">
+                    <div>按需引入</div>
+                    <div>Minimal Import</div>
+                  </div>
+                  <div
+                    class="type-error-log"
+                    v-if="item.compileErrors.minimal.length"
+                  >
+                    <div v-for="compileError in item.compileErrors.minimal">
+                      <pre>
+[{{compileError.location.join(', ')}}] {{compileError.message}}</pre
+                      >
+                    </div>
+                  </div>
+                  <div class="type-passed" v-else>No Error</div>
+                </el-card>
+              </el-col>
+            </el-row>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane label="Screenshots Compare" name="screenshots">
+          <div v-for="item in screenshotsCompareResult">
+            <h3>{{ item.testName }}({{item.screenshotDiffRatio}})</h3>
+            <el-row :gutter="10">
+              <el-col :span="4">
+                <el-card :body-style="{ padding: '0px' }">
+                  <el-image
+                    :src="SCREENSHOT_ROOT + item.testName + '.png'"
+                  ></el-image>
+                  <div class="import-type-title">
+                    <div>全量引入</div>
+                    <div>Full Import</div>
+                  </div>
+                </el-card>
+              </el-col>
+              <el-col :span="4">
+                <el-card :body-style="{ padding: '0px' }">
+                  <el-image
+                    :src="SCREENSHOT_ROOT + item.testName + '.minimal.png'"
+                  ></el-image>
+                  <div class="import-type-title">
+                    <div>按需引入</div>
+                    <div>Minimal Import</div>
+                  </div>
+                </el-card>
+              </el-col>
+              <el-col :span="4">
+                <el-card :body-style="{ padding: '0px' }">
+                  <el-image
+                    :src="SCREENSHOT_ROOT + item.testName + '.minimal.legacy.png'"
+                  ></el-image>
+                  <div class="import-type-title">
+                    <div>自注册方式按需引入</div>
+                    <div>Minimal Import with Self Registion</div>
+                  </div>
+                </el-card>
+              </el-col>
+              <el-col :span="4">
+                <el-card :body-style="{ padding: '0px' }">
+                  <el-image
+                    :src="SCREENSHOT_ROOT + item.testName + '.minimal.diff.png'"
+                  ></el-image>
+                  <div class="import-type-title">
+                    <div>按需引入差异</div>
+                    <div>Diff of Minimal Import</div>
+                  </div>
+                </el-card>
+              </el-col>
+              <el-col :span="4">
+                <el-card :body-style="{ padding: '0px' }">
+                  <el-image
+                    :src="SCREENSHOT_ROOT + item.testName + '.minimal.legacy.diff.png'"
+                  ></el-image>
+                  <div class="import-type-title">
+                    <div>自注册方式按需引入差异</div>
+                    <div>Diff of Minimal Import with Self Registion</div>
+                  </div>
+                </el-card>
+              </el-col>
+            </el-row>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
     </div>
     <script src="https://cdn.jsdelivr.net/npm/vue@^2.0.0/dist/vue.min.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/element-ui@2.14.1/lib/index.js"></script>
 
     <script>
-        const app = new Vue({
-            el: '#app',
-            data() {
-                return {
-                    typeCheckingResult: [],
-                    screenshotsCompareResult: [],
+      const app = new Vue({
+        el: '#app',
+        data() {
+          return {
+            typeCheckingResult: [],
+            screenshotsCompareResult: [],
 
-                    SCREENSHOT_ROOT: './tmp/screenshots/',
+            SCREENSHOT_ROOT: './tmp/screenshots/',
 
-                    tab: 'screenshots',
-                    splitterModel: 10
-                };
-            }
-        });
+            tab: 'screenshots',
+            splitterModel: 10
+          };
+        }
+      });
 
-        fetch('./tmp/result.json')
-            .then(response => response.json())
-            .then(json => {
-                const result = [];
-                function getCompilerErrorsCount({ full, minimal, minimalLegacy }) {
-                    return full.length + minimal.length + minimalLegacy.length;
-                }
-                function getScreenshotDiffRatio({ minimal, minimalLegacy }) {
-                    return minimal.ratio + minimalLegacy.ratio;
-                }
+      fetch('./tmp/result.json')
+        .then((response) => response.json())
+        .then((json) => {
+          const result = [];
+          function getCompilerErrorsCount({ full, minimal, minimalLegacy }) {
+            return full.length + minimal.length + minimalLegacy.length;
+          }
+          function getScreenshotDiffRatio({ minimal, minimalLegacy }) {
+            return minimal.ratio + minimalLegacy.ratio;
+          }
 
-                Object.keys(json).forEach(key => {
-                    result.push({
-                        ...json[key],
-                        compileErrorsCount: getCompilerErrorsCount(json[key].compileErrors),
-                        screenshotDiffRatio: getScreenshotDiffRatio(json[key].screenshotDiff),
-                        testName: key
-                    });
-                });
-                app.typeCheckingResult = result.slice().sort((a, b) => {
-                    return b.compileErrorsCount - a.compileErrorsCount;
-                });
-                app.screenshotsCompareResult = result.slice().sort((a, b) => {
-                    return b.screenshotDiffRatio - a.screenshotDiffRatio;
-                });
+          Object.keys(json).forEach((key) => {
+            result.push({
+              ...json[key],
+              compileErrorsCount: getCompilerErrorsCount(
+                json[key].compileErrors
+              ),
+              screenshotDiffRatio: getScreenshotDiffRatio(
+                json[key].screenshotDiff
+              ),
+              testName: key
             });
+          });
+          app.typeCheckingResult = result.slice().sort((a, b) => {
+            return b.compileErrorsCount - a.compileErrorsCount;
+          });
+          app.screenshotsCompareResult = result.slice().sort((a, b) => {
+            return b.screenshotDiffRatio - a.screenshotDiffRatio;
+          });
+        });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/e2e/template.html b/e2e/template.html
index 71c3b5b..664924f 100644
--- a/e2e/template.html
+++ b/e2e/template.html
@@ -1,26 +1,33 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@2.2.4/dist/jquery.min.js"></script>
-    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/dat.gui@0.6.5/build/dat.gui.min.js"></script>
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <script
+      type="text/javascript"
+      src="https://cdn.jsdelivr.net/npm/jquery@2.2.4/dist/jquery.min.js"
+    ></script>
+    <script
+      type="text/javascript"
+      src="https://cdn.jsdelivr.net/npm/dat.gui@0.6.5/build/dat.gui.min.js"
+    ></script>
     <title>Test Container</title>
-</head>
-<body>
+  </head>
+  <body>
     <style>
-        html, body {
-            margin: 0;
-            overflow: hidden;
-        }
-        #main {
-            position: absolute;
-            left: 0px;
-            top: 0px;
-            right: 0px;
-            bottom: 0px;
-        }
+      html,
+      body {
+        margin: 0;
+        overflow: hidden;
+      }
+      #main {
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        right: 0px;
+        bottom: 0px;
+      }
     </style>
     <div id="main"></div>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json
index ad6231a..8c0fd90 100644
--- a/e2e/tsconfig.json
+++ b/e2e/tsconfig.json
@@ -1,13 +1,13 @@
 {
-    "compilerOptions": {
-        "target": "ES6",
+  "compilerOptions": {
+    "target": "ES6",
 
-        "strict": true,
+    "strict": true,
 
-        "moduleResolution": "node",
-        "pretty": true,
-        "outDir": "tmp/tests",
+    "moduleResolution": "node",
+    "pretty": true,
+    "outDir": "tmp/tests",
 
-        "baseUrl": "./"
-    }
-}
\ No newline at end of file
+    "baseUrl": "./"
+  }
+}
diff --git a/e2e/webpack.config.js b/e2e/webpack.config.js
index 9d8c056..02610e6 100644
--- a/e2e/webpack.config.js
+++ b/e2e/webpack.config.js
@@ -1,20 +1,19 @@
 const path = require('path');
-const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+const BundleAnalyzerPlugin =
+  require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 const config = require('./config');
 
 module.exports = {
-    entry: path.resolve(__dirname, 'tmp/tests/area-basic.minimal.js'),
-    plugins: [
-        new BundleAnalyzerPlugin()
-    ],
-    resolve: {
-        alias: {
-            echarts: config.echartsDir,
-            zrender: config.zrenderDir
-        }
-    },
-    output: {
-        path: path.resolve(__dirname, 'tmp/bundles/'),
-        filename: 'area-basic.minimal.js'
+  entry: path.resolve(__dirname, 'tmp/tests/area-basic.minimal.js'),
+  plugins: [new BundleAnalyzerPlugin()],
+  resolve: {
+    alias: {
+      echarts: config.echartsDir,
+      zrender: config.zrenderDir
     }
-}
\ No newline at end of file
+  },
+  output: {
+    path: path.resolve(__dirname, 'tmp/bundles/'),
+    filename: 'area-basic.minimal.js'
+  }
+};
diff --git a/public/en/editor.html b/public/en/editor.html
index 2a92cdc..006c037 100644
--- a/public/en/editor.html
+++ b/public/en/editor.html
@@ -1,38 +1,45 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>ECharts Examples</title>
     <style>
-        body {
-            margin: 0;
-        }
-        ul, li {
-            margin: 0;
-            padding: 0;
-        }
+      body {
+        margin: 0;
+      }
+      ul,
+      li {
+        margin: 0;
+        padding: 0;
+      }
     </style>
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css">
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+    />
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css"
+    />
     <script src="//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index.js"></script>
-</head>
-<body>
+  </head>
+  <body>
     <div id="main"></div>
 
-    <link rel="stylesheet" href="../css/example-bundle.css">
+    <link rel="stylesheet" href="../css/example-bundle.css" />
     <script src="../js/example-bundle.js"></script>
 
     <script>
-        echartsExample.init(document.querySelector('#main'), {
-            locale: 'en',
-            cdnRoot: '../',
-            page: 'editor',
-            version: Date.now()
-        });
+      echartsExample.init(document.querySelector('#main'), {
+        locale: 'en',
+        cdnRoot: '../',
+        page: 'editor',
+        version: Date.now()
+      });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/public/en/index.html b/public/en/index.html
index 31a4ee6..f70e34c 100644
--- a/public/en/index.html
+++ b/public/en/index.html
@@ -1,39 +1,45 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>ECharts Examples</title>
     <style>
-        body {
-            margin: 0;
-        }
-        ul, li {
-            margin: 0;
-            padding: 0;
-        }
-
+      body {
+        margin: 0;
+      }
+      ul,
+      li {
+        margin: 0;
+        padding: 0;
+      }
     </style>
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css">
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+    />
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css"
+    />
     <script src="//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index.js"></script>
-</head>
-<body>
+  </head>
+  <body>
     <div id="main"></div>
 
-    <link rel="stylesheet" href="../css/example-bundle.css">
+    <link rel="stylesheet" href="../css/example-bundle.css" />
     <script src="../js/example-bundle.js"></script>
 
     <script>
-        echartsExample.init(document.querySelector('#main'), {
-            locale: 'en',
-            cdnRoot: '../',
-            page: 'explore',
-            version: Date.now()
-        });
+      echartsExample.init(document.querySelector('#main'), {
+        locale: 'en',
+        cdnRoot: '../',
+        page: 'explore',
+        version: Date.now()
+      });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/public/en/view.html b/public/en/view.html
index 1d1cc5a..a2e1d01 100644
--- a/public/en/view.html
+++ b/public/en/view.html
@@ -1,38 +1,45 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>ECharts Examples</title>
     <style>
-        body {
-            margin: 0;
-        }
-        ul, li {
-            margin: 0;
-            padding: 0;
-        }
+      body {
+        margin: 0;
+      }
+      ul,
+      li {
+        margin: 0;
+        padding: 0;
+      }
     </style>
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css">
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+    />
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css"
+    />
     <script src="//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index.js"></script>
-</head>
-<body>
+  </head>
+  <body>
     <div id="main"></div>
 
-    <link rel="stylesheet" href="../css/example-bundle.css">
+    <link rel="stylesheet" href="../css/example-bundle.css" />
     <script src="../js/example-bundle.js"></script>
 
     <script>
-        echartsExample.init(document.querySelector('#main'), {
-            locale: 'en',
-            cdnRoot: '../',
-            page: 'view',
-            version: Date.now()
-        });
+      echartsExample.init(document.querySelector('#main'), {
+        locale: 'en',
+        cdnRoot: '../',
+        page: 'view',
+        version: Date.now()
+      });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/public/zh/editor.html b/public/zh/editor.html
index ce69154..65edec1 100644
--- a/public/zh/editor.html
+++ b/public/zh/editor.html
@@ -1,38 +1,45 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>ECharts Examples</title>
     <style>
-        body {
-            margin: 0;
-        }
-        ul, li {
-            margin: 0;
-            padding: 0;
-        }
+      body {
+        margin: 0;
+      }
+      ul,
+      li {
+        margin: 0;
+        padding: 0;
+      }
     </style>
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css">
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+    />
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css"
+    />
     <script src="//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index.js"></script>
-</head>
-<body>
+  </head>
+  <body>
     <div id="main"></div>
 
-    <link rel="stylesheet" href="../css/example-bundle.css">
+    <link rel="stylesheet" href="../css/example-bundle.css" />
     <script src="../js/example-bundle.js"></script>
 
     <script>
-        echartsExample.init(document.querySelector('#main'), {
-            locale: 'zh',
-            cdnRoot: '../',
-            page: 'editor',
-            version: Date.now()
-        });
+      echartsExample.init(document.querySelector('#main'), {
+        locale: 'zh',
+        cdnRoot: '../',
+        page: 'editor',
+        version: Date.now()
+      });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/public/zh/index.html b/public/zh/index.html
index 4d534de..4012cfa 100644
--- a/public/zh/index.html
+++ b/public/zh/index.html
@@ -1,39 +1,45 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>ECharts Examples</title>
     <style>
-        body {
-            margin: 0;
-        }
-        ul, li {
-            margin: 0;
-            padding: 0;
-        }
-
+      body {
+        margin: 0;
+      }
+      ul,
+      li {
+        margin: 0;
+        padding: 0;
+      }
     </style>
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css">
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+    />
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css"
+    />
     <script src="//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index.js"></script>
-</head>
-<body>
+  </head>
+  <body>
     <div id="main"></div>
 
-    <link rel="stylesheet" href="../css/example-bundle.css">
+    <link rel="stylesheet" href="../css/example-bundle.css" />
     <script src="../js/example-bundle.js"></script>
 
     <script>
-        echartsExample.init(document.querySelector('#main'), {
-            locale: 'zh',
-            cdnRoot: '../',
-            page: 'explore',
-            version: Date.now()
-        });
+      echartsExample.init(document.querySelector('#main'), {
+        locale: 'zh',
+        cdnRoot: '../',
+        page: 'explore',
+        version: Date.now()
+      });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/public/zh/view.html b/public/zh/view.html
index b3d3927..5edb35b 100644
--- a/public/zh/view.html
+++ b/public/zh/view.html
@@ -1,38 +1,45 @@
 <!DOCTYPE html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>ECharts Examples</title>
     <style>
-        body {
-            margin: 0;
-        }
-        ul, li {
-            margin: 0;
-            padding: 0;
-        }
+      body {
+        margin: 0;
+      }
+      ul,
+      li {
+        margin: 0;
+        padding: 0;
+      }
     </style>
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css">
-    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css">
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+    />
+    <link
+      rel="stylesheet"
+      href="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/theme-chalk/index.css"
+    />
     <script src="//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"></script>
     <script src="//cdn.jsdelivr.net/npm/element-ui@2.13.2/lib/index.js"></script>
-</head>
-<body>
+  </head>
+  <body>
     <div id="main"></div>
 
-    <link rel="stylesheet" href="../css/example-bundle.css">
+    <link rel="stylesheet" href="../css/example-bundle.css" />
     <script src="../js/example-bundle.js"></script>
 
     <script>
-        echartsExample.init(document.querySelector('#main'), {
-            locale: 'zh',
-            cdnRoot: '../',
-            page: 'view',
-            version: Date.now()
-        });
+      echartsExample.init(document.querySelector('#main'), {
+        locale: 'zh',
+        cdnRoot: '../',
+        page: 'view',
+        version: Date.now()
+      });
     </script>
-</body>
-</html>
\ No newline at end of file
+  </body>
+</html>
diff --git a/src/common/config.js b/src/common/config.js
index faab503..c737a41 100644
--- a/src/common/config.js
+++ b/src/common/config.js
@@ -1,134 +1,138 @@
-
 export const EXAMPLE_CATEGORIES = [
-    'line',
-    'bar',
-    'pie',
-    'scatter',
-    'map',
-    'candlestick',
-    'radar',
-    'boxplot',
-    'heatmap',
-    'graph',
-    'lines',
-    'tree',
-    'treemap',
-    'sunburst',
-    'parallel',
-    'sankey',
-    'funnel',
-    'gauge',
-    'pictorialBar',
-    'themeRiver',
-    'calendar',
-    'custom',
+  'line',
+  'bar',
+  'pie',
+  'scatter',
+  'map',
+  'candlestick',
+  'radar',
+  'boxplot',
+  'heatmap',
+  'graph',
+  'lines',
+  'tree',
+  'treemap',
+  'sunburst',
+  'parallel',
+  'sankey',
+  'funnel',
+  'gauge',
+  'pictorialBar',
+  'themeRiver',
+  'calendar',
+  'custom',
 
-    'dataset',
-    'dataZoom',
-    'drag',
-    'rich',
+  'dataset',
+  'dataZoom',
+  'drag',
+  'rich',
 
-    'globe',
-    'bar3D',
-    'scatter3D',
-    'surface',
-    'map3D',
-    'lines3D',
-    'line3D',
-    'scatterGL',
-    'linesGL',
-    'flowGL',
-    'graphGL'
+  'globe',
+  'bar3D',
+  'scatter3D',
+  'surface',
+  'map3D',
+  'lines3D',
+  'line3D',
+  'scatterGL',
+  'linesGL',
+  'flowGL',
+  'graphGL'
 ];
 
 export const THEMES = {
-    default: [
-        '#5470c6',
-        '#91cc75',
-        '#fac858',
-        '#ee6666',
-        '#73c0de',
-        '#3ba272',
-        '#fc8452',
-        '#9a60b4',
-        '#ea7ccc'
-    ],
-    dark: [
-        '#4992ff',
-        '#7cffb2',
-        '#fddd60',
-        '#ff6e76',
-        '#58d9f9',
-        '#05c091',
-        '#ff8a45',
-        '#8d48e3',
-        '#dd79ff'
-    ]
+  default: [
+    '#5470c6',
+    '#91cc75',
+    '#fac858',
+    '#ee6666',
+    '#73c0de',
+    '#3ba272',
+    '#fc8452',
+    '#9a60b4',
+    '#ea7ccc'
+  ],
+  dark: [
+    '#4992ff',
+    '#7cffb2',
+    '#fddd60',
+    '#ff6e76',
+    '#58d9f9',
+    '#05c091',
+    '#ff8a45',
+    '#8d48e3',
+    '#dd79ff'
+  ]
 };
 
 export const BLACK_MAP = (function (list) {
-    const map = {};
-    for (var i = 0; i < list.length; i++) {
-        map[list[i]] = 1;
-    }
-    return location.href.indexOf('github.io') >= 0 ? {} : map;
+  const map = {};
+  for (var i = 0; i < list.length; i++) {
+    map[list[i]] = 1;
+  }
+  return location.href.indexOf('github.io') >= 0 ? {} : map;
 })([
-    'effectScatter-map',
-    'geo-lines',
-    'geo-map-scatter',
-    'heatmap-map',
-    'lines-airline',
-    'map-china',
-    'map-china-dataRange',
-    'map-labels',
-    'map-locate',
-    'map-province',
-    'map-world',
-    'map-world-dataRange',
-    'scatter-map',
-    'scatter-map-brush',
-    'scatter-weibo',
-    'scatter-world-population',
-    'geo3d',
-    'geo3d-with-different-height',
-    'globe-country-carousel',
-    'globe-with-echarts-surface',
-    'map3d-alcohol-consumption',
-    'map3d-wood-map',
-    'scattergl-weibo'
+  'effectScatter-map',
+  'geo-lines',
+  'geo-map-scatter',
+  'heatmap-map',
+  'lines-airline',
+  'map-china',
+  'map-china-dataRange',
+  'map-labels',
+  'map-locate',
+  'map-province',
+  'map-world',
+  'map-world-dataRange',
+  'scatter-map',
+  'scatter-map-brush',
+  'scatter-weibo',
+  'scatter-world-population',
+  'geo3d',
+  'geo3d-with-different-height',
+  'globe-country-carousel',
+  'globe-with-echarts-surface',
+  'map3d-alcohol-consumption',
+  'map3d-wood-map',
+  'scattergl-weibo'
 ]);
 
-
 const URL_PARAMS = {};
-(location.search || '').substr(1).split('&').forEach(function (item) {
+(location.search || '')
+  .substr(1)
+  .split('&')
+  .forEach(function (item) {
     const kv = item.split('=');
     URL_PARAMS[kv[0]] = kv[1];
-});
+  });
 
-export {URL_PARAMS};
+export { URL_PARAMS };
 
 export const SUPPORT_WEBP = (function () {
-    var elem = document.createElement('canvas');
-    elem.width = elem.height = 1;
-    if (!!(elem.getContext && elem.getContext('2d'))) {
-        // was able or not to get WebP representation
-        return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
-    }
-    // very old browser like IE 8, canvas not supported
-    return false;
+  var elem = document.createElement('canvas');
+  elem.width = elem.height = 1;
+  if (!!(elem.getContext && elem.getContext('2d'))) {
+    // was able or not to get WebP representation
+    return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
+  }
+  // very old browser like IE 8, canvas not supported
+  return false;
 })();
 
 export const SCRIPT_URLS = {
-    echartsMinJS: 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js',
-    echartsDir: 'https://cdn.jsdelivr.net/npm/echarts@5',
+  echartsMinJS: 'https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js',
+  echartsDir: 'https://cdn.jsdelivr.net/npm/echarts@5',
 
-    localEChartsMinJS: 'http://localhost/echarts/dist/echarts.js',
-    localEChartsDir: 'http://localhost/echarts',
+  localEChartsMinJS: 'http://localhost/echarts/dist/echarts.js',
+  localEChartsDir: 'http://localhost/echarts',
 
-    echartsStatMinJS: 'https://cdn.jsdelivr.net/npm/echarts-stat@latest/dist/ecStat.min.js',
-    // echartsGLMinJS: 'http://localhost/echarts-gl/dist/echarts-gl.min.js',
-    echartsGLMinJS: 'https://cdn.jsdelivr.net/npm/echarts-gl@2/dist/echarts-gl.min.js',
-    datGUIMinJS: 'https://cdn.jsdelivr.net/npm/dat.gui@0.6.5/build/dat.gui.min.js',
-    monacoDir: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.21.2/min/vs',
-    aceDir: 'https://cdn.jsdelivr.net/npm/ace-builds@1.4.12/src-min-noconflict'
+  echartsStatMinJS:
+    'https://cdn.jsdelivr.net/npm/echarts-stat@latest/dist/ecStat.min.js',
+  // echartsGLMinJS: 'http://localhost/echarts-gl/dist/echarts-gl.min.js',
+  echartsGLMinJS:
+    'https://cdn.jsdelivr.net/npm/echarts-gl@2/dist/echarts-gl.min.js',
+  datGUIMinJS:
+    'https://cdn.jsdelivr.net/npm/dat.gui@0.6.5/build/dat.gui.min.js',
+  monacoDir: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.21.2/min/vs',
+  aceDir: 'https://cdn.jsdelivr.net/npm/ace-builds@1.4.12/src-min-noconflict'
 };
diff --git a/src/common/helper.js b/src/common/helper.js
index ac4397a..811442e 100644
--- a/src/common/helper.js
+++ b/src/common/helper.js
@@ -1,60 +1,59 @@
-
 const promisesCache = {};
 
 export function loadScriptsAsync(scripts) {
-    return Promise.all(scripts.map(function (scriptUrl) {
-        if (typeof scriptUrl === 'string') {
-            scriptUrl = {
-                url: scriptUrl,
-                // TODO Not supported type
-                type: scriptUrl.match(/\.css$/) ? 'css' : 'js'
-            };
-        }
-        if (promisesCache[scriptUrl.url]) {
-            return promisesCache[scriptUrl.url];
+  return Promise.all(
+    scripts.map(function (scriptUrl) {
+      if (typeof scriptUrl === 'string') {
+        scriptUrl = {
+          url: scriptUrl,
+          // TODO Not supported type
+          type: scriptUrl.match(/\.css$/) ? 'css' : 'js'
+        };
+      }
+      if (promisesCache[scriptUrl.url]) {
+        return promisesCache[scriptUrl.url];
+      }
+      const promise = new Promise((resolve, reject) => {
+        if (scriptUrl.type === 'js') {
+          const script = document.createElement('script');
+          script.src = scriptUrl.url;
+          script.async = false;
+          script.onload = function () {
+            resolve();
+          };
+          script.onerror = function () {
+            reject();
+          };
+          document.body.appendChild(script);
+        } else if (scriptUrl.type === 'css') {
+          const link = document.createElement('link');
+          link.rel = 'stylesheet';
+          link.href = scriptUrl.url;
+          link.onload = function () {
+            resolve();
+          };
+          link.onerror = function () {
+            reject();
+          };
+          document.body.appendChild(link);
         }
-        const promise = new Promise((resolve, reject) => {
-            if (scriptUrl.type === 'js') {
-                const script = document.createElement('script');
-                script.src = scriptUrl.url;
-                script.async = false;
-                script.onload = function () {
-                    resolve();
-                };
-                script.onerror = function () {
-                    reject();
-                };
-                document.body.appendChild(script);
-            }
-            else if (scriptUrl.type === 'css') {
-                const link = document.createElement('link');
-                link.rel = 'stylesheet';
-                link.href = scriptUrl.url;
-                link.onload = function () {
-                    resolve();
-                };
-                link.onerror = function () {
-                    reject();
-                };
-                document.body.appendChild(link);
-            }
-        });
-        promisesCache[scriptUrl.url] = promise;
-        return promise;
-    }));
+      });
+      promisesCache[scriptUrl.url] = promise;
+      return promise;
+    })
+  );
 }
 
 export function downloadBlob(blob, fileName) {
-    // for IE
-    if (typeof window.navigator.msSaveBlob === 'function') {
-        window.navigator.msSaveOrOpenBlob(blob, fileName);
-    }
-    else {
-        const a = document.createElement('a');
-        a.href = URL.createObjectURL(blob);
-        a.download = fileName;
-        a.click();
-        // should revoke the blob url after the download
-        URL.revokeObjectURL(a.href);
-    }
-}
\ No newline at end of file
+  // for IE
+  if (typeof window.navigator.msSaveBlob === 'function') {
+    window.navigator.msSaveOrOpenBlob(blob, fileName);
+  } else {
+    const a = document.createElement('a');
+    a.href = URL.createObjectURL(blob);
+    a.download = fileName;
+    a.click();
+    // should revoke the blob url after the download
+    URL.revokeObjectURL(a.href);
+  }
+}
diff --git a/src/common/i18n.js b/src/common/i18n.js
index 369cba4..88e6493 100644
--- a/src/common/i18n.js
+++ b/src/common/i18n.js
@@ -1,135 +1,135 @@
 export default {
-    en: {
-        editor: {
-            run: 'Run',
-            errorInEditor: 'Errors exist in code!',
-            chartOK: 'Chart has been generated successfully, ',
+  en: {
+    editor: {
+      run: 'Run',
+      errorInEditor: 'Errors exist in code!',
+      chartOK: 'Chart has been generated successfully, ',
 
-            darkMode: 'Dark Mode',
-            enableDecal: 'Decal Pattern',
-            // lightMode: 'Light Mode',
+      darkMode: 'Dark Mode',
+      enableDecal: 'Decal Pattern',
+      // lightMode: 'Light Mode',
 
-            renderCfgTitle: 'Render',
-            renderer: 'Renderer',
-            useDirtyRect: 'Use Dirty Rect',
+      renderCfgTitle: 'Render',
+      renderer: 'Renderer',
+      useDirtyRect: 'Use Dirty Rect',
 
-            download: 'Download',
+      download: 'Download',
 
-            edit: 'Edit',
-            monacoMode: 'Enable Type Checking',
+      edit: 'Edit',
+      monacoMode: 'Enable Type Checking',
 
-            tabEditor: 'Edit Example',
-            tabFullCodePreview: 'Full Code',
-            tabOptionPreview: 'Option Preview',
-            minimalBundle: 'Minimal Bundle'
-        },
+      tabEditor: 'Edit Example',
+      tabFullCodePreview: 'Full Code',
+      tabOptionPreview: 'Option Preview',
+      minimalBundle: 'Minimal Bundle'
+    },
 
-        chartTypes: {
-            line: 'Line',
-            bar: 'Bar',
-            pie: 'Pie',
-            scatter: 'Scatter',
-            map: 'GEO/Map',
-            candlestick: 'Candlestick',
-            radar: 'Radar',
-            boxplot: 'Boxplot',
-            heatmap: 'Heatmap',
-            graph: 'Graph',
-            lines: 'Lines',
-            tree: 'Tree',
-            treemap: 'Treemap',
-            sunburst: 'Sunburst',
-            parallel: 'Parallel',
-            sankey: 'Sankey',
-            funnel: 'Funnel',
-            gauge: 'Gauge',
-            pictorialBar: 'PictorialBar',
-            themeRiver: 'ThemeRiver',
-            calendar: 'Calendar',
-            custom: 'Custom',
+    chartTypes: {
+      line: 'Line',
+      bar: 'Bar',
+      pie: 'Pie',
+      scatter: 'Scatter',
+      map: 'GEO/Map',
+      candlestick: 'Candlestick',
+      radar: 'Radar',
+      boxplot: 'Boxplot',
+      heatmap: 'Heatmap',
+      graph: 'Graph',
+      lines: 'Lines',
+      tree: 'Tree',
+      treemap: 'Treemap',
+      sunburst: 'Sunburst',
+      parallel: 'Parallel',
+      sankey: 'Sankey',
+      funnel: 'Funnel',
+      gauge: 'Gauge',
+      pictorialBar: 'PictorialBar',
+      themeRiver: 'ThemeRiver',
+      calendar: 'Calendar',
+      custom: 'Custom',
 
-            dataset: 'Dataset',
-            dataZoom: 'DataZoom',
-            drag: 'Drag',
-            rich: 'Rich Text',
+      dataset: 'Dataset',
+      dataZoom: 'DataZoom',
+      drag: 'Drag',
+      rich: 'Rich Text',
 
-            globe: '3D Globe',
-            bar3D: '3D Bar',
-            scatter3D: '3D Scatter',
-            surface: '3D Surface',
-            map3D: '3D Map',
-            lines3D: '3D Lines',
-            line3D: '3D Line',
-            scatterGL: 'Scatter GL',
-            linesGL: 'Lines GL',
-            flowGL: 'Flow GL',
-            graphGL: 'Graph GL'
-        }
-    },
-    zh: {
-        editor: {
-            run: '运行',
-            errorInEditor: '编辑器内容有误!',
-            chartOK: '图表已生成, ',
+      globe: '3D Globe',
+      bar3D: '3D Bar',
+      scatter3D: '3D Scatter',
+      surface: '3D Surface',
+      map3D: '3D Map',
+      lines3D: '3D Lines',
+      line3D: '3D Line',
+      scatterGL: 'Scatter GL',
+      linesGL: 'Lines GL',
+      flowGL: 'Flow GL',
+      graphGL: 'Graph GL'
+    }
+  },
+  zh: {
+    editor: {
+      run: '运行',
+      errorInEditor: '编辑器内容有误!',
+      chartOK: '图表已生成, ',
 
-            darkMode: '深色模式',
-            enableDecal: '无障碍花纹',
-            // lightMode: '浅色模式',
+      darkMode: '深色模式',
+      enableDecal: '无障碍花纹',
+      // lightMode: '浅色模式',
 
-            renderCfgTitle: '渲染设置',
-            useDirtyRect: '开启脏矩形优化',
-            renderer: '渲染模式',
-            download: '下载示例',
+      renderCfgTitle: '渲染设置',
+      useDirtyRect: '开启脏矩形优化',
+      renderer: '渲染模式',
+      download: '下载示例',
 
-            edit: '编辑',
-            monacoMode: '开启类型检查',
+      edit: '编辑',
+      monacoMode: '开启类型检查',
 
-            tabEditor: '示例编辑',
-            tabFullCodePreview: '完整代码',
-            tabOptionPreview: '配置项',
-            minimalBundle: '按需引入'
-        },
+      tabEditor: '示例编辑',
+      tabFullCodePreview: '完整代码',
+      tabOptionPreview: '配置项',
+      minimalBundle: '按需引入'
+    },
 
-        chartTypes: {
-            line: '折线图',
-            bar: '柱状图',
-            pie: '饼图',
-            scatter: '散点图',
-            map: '地理坐标/地图',
-            candlestick: 'K 线图',
-            radar: '雷达图',
-            boxplot: '盒须图',
-            heatmap: '热力图',
-            graph: '关系图',
-            lines: '路径图',
-            tree: '树图',
-            treemap: '矩形树图',
-            sunburst: '旭日图',
-            parallel: '平行坐标系',
-            sankey: '桑基图',
-            funnel: '漏斗图',
-            gauge: '仪表盘',
-            pictorialBar: '象形柱图',
-            themeRiver: '主题河流图',
-            calendar: '日历坐标系',
-            custom: '自定义系列',
+    chartTypes: {
+      line: '折线图',
+      bar: '柱状图',
+      pie: '饼图',
+      scatter: '散点图',
+      map: '地理坐标/地图',
+      candlestick: 'K 线图',
+      radar: '雷达图',
+      boxplot: '盒须图',
+      heatmap: '热力图',
+      graph: '关系图',
+      lines: '路径图',
+      tree: '树图',
+      treemap: '矩形树图',
+      sunburst: '旭日图',
+      parallel: '平行坐标系',
+      sankey: '桑基图',
+      funnel: '漏斗图',
+      gauge: '仪表盘',
+      pictorialBar: '象形柱图',
+      themeRiver: '主题河流图',
+      calendar: '日历坐标系',
+      custom: '自定义系列',
 
-            dataset: '数据集',
-            dataZoom: '数据区域缩放',
-            drag: '拖拽',
-            rich: '富文本',
+      dataset: '数据集',
+      dataZoom: '数据区域缩放',
+      drag: '拖拽',
+      rich: '富文本',
 
-            globe: '3D 地球',
-            bar3D: '3D 柱状图',
-            scatter3D: '3D 散点图',
-            surface: '3D 曲面',
-            map3D: '3D 地图',
-            lines3D: '3D 路径图',
-            line3D: '3D 折线图',
-            scatterGL: 'GL 散点图',
-            linesGL: 'GL 路径图',
-            flowGL: 'GL 矢量场图',
-            graphGL: 'GL 关系图'
-        }
+      globe: '3D 地球',
+      bar3D: '3D 柱状图',
+      scatter3D: '3D 散点图',
+      surface: '3D 曲面',
+      map3D: '3D 地图',
+      lines3D: '3D 路径图',
+      line3D: '3D 折线图',
+      scatterGL: 'GL 散点图',
+      linesGL: 'GL 路径图',
+      flowGL: 'GL 矢量场图',
+      graphGL: 'GL 关系图'
     }
-};
\ No newline at end of file
+  }
+};
diff --git a/src/common/store.js b/src/common/store.js
index 95a0d0f..54cc80a 100644
--- a/src/common/store.js
+++ b/src/common/store.js
@@ -1,58 +1,60 @@
-
 // import * as matter from 'gray-matter';
-import {URL_PARAMS} from '../common/config';
+import { URL_PARAMS } from '../common/config';
 
 export const store = {
-    cdnRoot: '',
-    version: '',
-    locale: '',
+  cdnRoot: '',
+  version: '',
+  locale: '',
 
-    darkMode: URL_PARAMS.theme === 'dark',
-    enableDecal: 'decal' in URL_PARAMS,
-    renderer: URL_PARAMS.renderer || 'canvas',
+  darkMode: URL_PARAMS.theme === 'dark',
+  enableDecal: 'decal' in URL_PARAMS,
+  renderer: URL_PARAMS.renderer || 'canvas',
 
-    typeCheck: URL_PARAMS.editor === 'monaco',
-    useDirtyRect: 'useDirtyRect' in URL_PARAMS,
+  typeCheck: URL_PARAMS.editor === 'monaco',
+  useDirtyRect: 'useDirtyRect' in URL_PARAMS,
 
-    runCode: '',
-    sourceCode: '',
+  runCode: '',
+  sourceCode: '',
 
-    runHash: '',
+  runHash: '',
 
-    isMobile: window.innerWidth < 600,
+  isMobile: window.innerWidth < 600,
 
-    editorStatus: {
-        type: '',
-        message: ''
-    }
+  editorStatus: {
+    type: '',
+    message: ''
+  }
 };
 
 export function loadExampleCode() {
-    return new Promise(resolve => {
-        const dataRoot = URL_PARAMS.gl ? 'data-gl' : 'data';
-        $.ajax(
-            store.typeCheck
-                ?  `${store.cdnRoot}/examples/ts/${URL_PARAMS.c}.ts?_v_${store.version}`
-                : `${store.cdnRoot}/${dataRoot}/${URL_PARAMS.c}.js?_v_${store.version}`,
-            {
-                dataType: 'text',
-                success: (data) => {
-                    resolve(data);
-                }
-            }
-        );
-    });
+  return new Promise((resolve) => {
+    const dataRoot = URL_PARAMS.gl ? 'data-gl' : 'data';
+    $.ajax(
+      store.typeCheck
+        ? `${store.cdnRoot}/examples/ts/${URL_PARAMS.c}.ts?_v_${store.version}`
+        : `${store.cdnRoot}/${dataRoot}/${URL_PARAMS.c}.js?_v_${store.version}`,
+      {
+        dataType: 'text',
+        success: (data) => {
+          resolve(data);
+        }
+      }
+    );
+  });
 }
 
 export function parseSourceCode(code) {
-    return code
-        // remove front matter
-        .replace(/\/\*[\w\W]*?\*\//, '').trim()
-        // ts code needs add `export {}` to be a module. remove it.
-        .replace(/export\s+\{\s*\}\s*;?$/g, '');
+  return (
+    code
+      // remove front matter
+      .replace(/\/\*[\w\W]*?\*\//, '')
+      .trim()
+      // ts code needs add `export {}` to be a module. remove it.
+      .replace(/export\s+\{\s*\}\s*;?$/g, '')
+  );
 }
 
 let hashId = 123;
 export function updateRunHash() {
-    store.runHash = hashId++;
-}
\ No newline at end of file
+  store.runHash = hashId++;
+}
diff --git a/src/dep/showDebugDirtyRect.js b/src/dep/showDebugDirtyRect.js
index fcd6e64..5ee3204 100644
--- a/src/dep/showDebugDirtyRect.js
+++ b/src/dep/showDebugDirtyRect.js
@@ -1,79 +1,83 @@
 var DebugRect = (function () {
-    function DebugRect(style) {
-        var dom = this.dom = document.createElement('div');
-        dom.className = 'ec-debug-dirty-rect';
-        style = Object.assign({}, style);
-        Object.assign(style, {
-            backgroundColor: 'rgba(0, 0, 255, 0.2)',
-            border: '1px solid #00f'
-        });
-        dom.style.cssText = "\nposition: absolute;\nopacity: 0;\ntransition: opacity 0.5s linear;\npointer-events: none;\n";
-        for (var key in style) {
-            if (style.hasOwnProperty(key)) {
-                dom.style[key] = style[key];
-            }
-        }
+  function DebugRect(style) {
+    var dom = (this.dom = document.createElement('div'));
+    dom.className = 'ec-debug-dirty-rect';
+    style = Object.assign({}, style);
+    Object.assign(style, {
+      backgroundColor: 'rgba(0, 0, 255, 0.2)',
+      border: '1px solid #00f'
+    });
+    dom.style.cssText =
+      '\nposition: absolute;\nopacity: 0;\ntransition: opacity 0.5s linear;\npointer-events: none;\n';
+    for (var key in style) {
+      if (style.hasOwnProperty(key)) {
+        dom.style[key] = style[key];
+      }
     }
-    DebugRect.prototype.update = function (rect) {
-        var domStyle = this.dom.style;
-        domStyle.width = rect.width + 'px';
-        domStyle.height = rect.height + 'px';
-        domStyle.left = rect.x + 'px';
-        domStyle.top = rect.y + 'px';
-    };
-    DebugRect.prototype.hide = function () {
-        this.dom.style.opacity = '0';
-    };
-    DebugRect.prototype.show = function () {
-        var _this = this;
-        clearTimeout(this._hideTimeout);
-        this.dom.style.opacity = '1';
-        this._hideTimeout = setTimeout(function () {
-            _this.hide();
-        }, 500);
-    };
-    return DebugRect;
-}());
+  }
+  DebugRect.prototype.update = function (rect) {
+    var domStyle = this.dom.style;
+    domStyle.width = rect.width + 'px';
+    domStyle.height = rect.height + 'px';
+    domStyle.left = rect.x + 'px';
+    domStyle.top = rect.y + 'px';
+  };
+  DebugRect.prototype.hide = function () {
+    this.dom.style.opacity = '0';
+  };
+  DebugRect.prototype.show = function () {
+    var _this = this;
+    clearTimeout(this._hideTimeout);
+    this.dom.style.opacity = '1';
+    this._hideTimeout = setTimeout(function () {
+      _this.hide();
+    }, 500);
+  };
+  return DebugRect;
+})();
 export default function (zr, opts) {
-    opts = opts || {};
-    var painter = zr.painter;
-    if (!painter.getLayers) {
-        throw new Error('Debug dirty rect can only been used on canvas renderer.');
-    }
-    if (painter.isSingleCanvas()) {
-        throw new Error('Debug dirty rect can only been used on zrender inited with container.');
-    }
-    var debugViewRoot = document.createElement('div');
-    debugViewRoot.style.cssText = "\nposition:absolute;\nleft:0;\ntop:0;\nright:0;\nbottom:0;\npointer-events:none;\n";
-    debugViewRoot.className = 'ec-debug-dirty-rect-container';
-    var debugRects = [];
-    var dom = zr.dom;
-    dom.appendChild(debugViewRoot);
-    var computedStyle = getComputedStyle(dom);
-    if (computedStyle.position === 'static') {
-        dom.style.position = 'relative';
-    }
-    zr.on('rendered', function () {
-        if (painter.getLayers) {
-            var idx_1 = 0;
-            painter.eachBuiltinLayer(function (layer) {
-                if (!layer.debugGetPaintRects) {
-                    return;
-                }
-                var paintRects = layer.debugGetPaintRects();
-                for (var i = 0; i < paintRects.length; i++) {
-                    if (!debugRects[idx_1]) {
-                        debugRects[idx_1] = new DebugRect(opts.style);
-                        debugViewRoot.appendChild(debugRects[idx_1].dom);
-                    }
-                    debugRects[idx_1].show();
-                    debugRects[idx_1].update(paintRects[i]);
-                    idx_1++;
-                }
-            });
-            for (var i = idx_1; i < debugRects.length; i++) {
-                debugRects[i].hide();
-            }
+  opts = opts || {};
+  var painter = zr.painter;
+  if (!painter.getLayers) {
+    throw new Error('Debug dirty rect can only been used on canvas renderer.');
+  }
+  if (painter.isSingleCanvas()) {
+    throw new Error(
+      'Debug dirty rect can only been used on zrender inited with container.'
+    );
+  }
+  var debugViewRoot = document.createElement('div');
+  debugViewRoot.style.cssText =
+    '\nposition:absolute;\nleft:0;\ntop:0;\nright:0;\nbottom:0;\npointer-events:none;\n';
+  debugViewRoot.className = 'ec-debug-dirty-rect-container';
+  var debugRects = [];
+  var dom = zr.dom;
+  dom.appendChild(debugViewRoot);
+  var computedStyle = getComputedStyle(dom);
+  if (computedStyle.position === 'static') {
+    dom.style.position = 'relative';
+  }
+  zr.on('rendered', function () {
+    if (painter.getLayers) {
+      var idx_1 = 0;
+      painter.eachBuiltinLayer(function (layer) {
+        if (!layer.debugGetPaintRects) {
+          return;
         }
-    });
+        var paintRects = layer.debugGetPaintRects();
+        for (var i = 0; i < paintRects.length; i++) {
+          if (!debugRects[idx_1]) {
+            debugRects[idx_1] = new DebugRect(opts.style);
+            debugViewRoot.appendChild(debugRects[idx_1].dom);
+          }
+          debugRects[idx_1].show();
+          debugRects[idx_1].update(paintRects[i]);
+          idx_1++;
+        }
+      });
+      for (var i = idx_1; i < debugRects.length; i++) {
+        debugRects[i].hide();
+      }
+    }
+  });
 }
diff --git a/src/editor/CodeAce.vue b/src/editor/CodeAce.vue
index ef2a34c..72258ca 100644
--- a/src/editor/CodeAce.vue
+++ b/src/editor/CodeAce.vue
@@ -1,116 +1,115 @@
 <template>
-<div class="ace-editor-main" v-loading="loading"></div>
+  <div class="ace-editor-main" v-loading="loading"></div>
 </template>
 
 <script>
-
-import {keywords} from '../data/option-keywords';
-import {loadScriptsAsync} from '../common/helper';
-import {store} from '../common/store';
-import {SCRIPT_URLS} from '../common/config';
+import { keywords } from '../data/option-keywords';
+import { loadScriptsAsync } from '../common/helper';
+import { store } from '../common/store';
+import { SCRIPT_URLS } from '../common/config';
 
 function ensureACE() {
-    if (typeof ace === 'undefined') {
-        return loadScriptsAsync([
-            SCRIPT_URLS.aceDir + '/ace.js',
-            SCRIPT_URLS.aceDir + '/ext-language_tools.js'
-        ]).then(function () {
-            const lnTools = ace.require('ace/ext/language_tools');
-
-            const completions = [];
-            keywords.forEach(keyword => {
-                completions.push({
-                    caption: keyword.name,
-                    value: keyword.name,
-                    score: keyword.count,
-                    metal: 'local'
-                });
-            });
+  if (typeof ace === 'undefined') {
+    return loadScriptsAsync([
+      SCRIPT_URLS.aceDir + '/ace.js',
+      SCRIPT_URLS.aceDir + '/ext-language_tools.js'
+    ]).then(function () {
+      const lnTools = ace.require('ace/ext/language_tools');
+
+      const completions = [];
+      keywords.forEach((keyword) => {
+        completions.push({
+          caption: keyword.name,
+          value: keyword.name,
+          score: keyword.count,
+          metal: 'local'
+        });
+      });
 
-            lnTools.addCompleter({
-                getCompletions: function (editor, session, pos, prefix, callback) {
-                    callback(null, completions);
-                }
-            });
-        })
-    }
-    return Promise.resolve();
+      lnTools.addCompleter({
+        getCompletions: function (editor, session, pos, prefix, callback) {
+          callback(null, completions);
+        }
+      });
+    });
+  }
+  return Promise.resolve();
 }
 
 export default {
-
-    props: ['initialCode'],
-
-    data() {
-        return {
-            shared: store,
-            loading: false
-        }
-    },
-
-    mounted() {
-        this.loading = true;
-        ensureACE().then(() => {
-            this.loading = false;
-            const editor = ace.edit(this.$el);
-            editor.getSession().setMode('ace/mode/javascript');
-            editor.setOptions({
-                enableBasicAutocompletion: true,
-                enableSnippets: true,
-                enableLiveAutocompletion: true
-            });
-
-            this._editor = editor;
-
-            editor.on('change', () => {
-                store.sourceCode =
-                    store.runCode = editor.getValue();
-            });
-
-            if (this.initialCode) {
-                this.setInitialCode(this.initialCode);
-            }
+  props: ['initialCode'],
+
+  data() {
+    return {
+      shared: store,
+      loading: false
+    };
+  },
+
+  mounted() {
+    this.loading = true;
+    ensureACE().then(() => {
+      this.loading = false;
+      const editor = ace.edit(this.$el);
+      editor.getSession().setMode('ace/mode/javascript');
+      editor.setOptions({
+        enableBasicAutocompletion: true,
+        enableSnippets: true,
+        enableLiveAutocompletion: true
+      });
+
+      this._editor = editor;
+
+      editor.on('change', () => {
+        store.sourceCode = store.runCode = editor.getValue();
+      });
+
+      if (this.initialCode) {
+        this.setInitialCode(this.initialCode);
+      }
+    });
+  },
+
+  methods: {
+    setInitialCode(code) {
+      if (this._editor && code) {
+        this._editor.setValue(code || '');
+        this._editor.selection.setSelectionRange({
+          start: {
+            row: 1,
+            column: 4
+          },
+          end: {
+            row: 1,
+            column: 4
+          }
         });
-    },
-
-    methods: {
-        setInitialCode(code) {
-            if (this._editor && code) {
-                this._editor.setValue(code || '');
-                this._editor.selection.setSelectionRange({
-                    start: {
-                        row:1,
-                        column: 4
-                    }, end: {
-                        row:1,
-                        column: 4
-                    }
-                });
-            }
-        }
-    },
+      }
+    }
+  },
 
-    watch: {
-        initialCode(newVal) {
-            this.setInitialCode(newVal);
-        }
+  watch: {
+    initialCode(newVal) {
+      this.setInitialCode(newVal);
     }
-}
+  }
+};
 </script>
 
 <style lang="scss">
 .ace-editor-main {
-    font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
-    font-size: 12px;
-    line-height: 18px;
-    padding: 10px;
-    // height: 100%;
-
-    // Fix safari
-    position: absolute;
-    left: 0;
-    top: 0;
-    bottom: 0;
-    right: 0;
+  font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas',
+    monospace;
+  font-size: 12px;
+  line-height: 18px;
+  padding: 10px;
+  // height: 100%;
+
+  // Fix safari
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  right: 0;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/editor/CodeMonaco.vue b/src/editor/CodeMonaco.vue
index 128c900..695af20 100644
--- a/src/editor/CodeMonaco.vue
+++ b/src/editor/CodeMonaco.vue
@@ -1,53 +1,56 @@
 <template>
-<div class="monaco-editor-main" v-loading="loading"></div>
+  <div class="monaco-editor-main" v-loading="loading"></div>
 </template>
 
 <script>
-
-import {loadScriptsAsync} from '../common/helper';
-import {store} from '../common/store';
-import {SCRIPT_URLS, URL_PARAMS} from '../common/config';
+import { loadScriptsAsync } from '../common/helper';
+import { store } from '../common/store';
+import { SCRIPT_URLS, URL_PARAMS } from '../common/config';
 import { ensureECharts } from './Preview.vue';
 
 function loadTypes() {
-    return fetch(
-        ('local' in URL_PARAMS
-                ? SCRIPT_URLS.localEChartsDir : SCRIPT_URLS.echartsDir) + '/types/dist/echarts.d.ts', {
-        mode: 'cors'
-    }).then(response => response.text()).then(code => {
-
-        // validation settings
-        monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
-            noSemanticValidation: false,
-            noSyntaxValidation: false
-        });
-
-        // compiler options
-        monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
-            target: monaco.languages.typescript.ScriptTarget.ES6,
-            allowNonTsExtensions: true,
-            noResolve: false
-        });
-
-        // console.log('file:///node_modules/@types/' + res[i].path);
-        monaco.languages.typescript.typescriptDefaults.addExtraLib(
-            code,
-            // https://github.com/microsoft/monaco-editor/issues/667#issuecomment-468164794
-            'file:///node_modules/@types/echarts/echarts.d.ts'
-        );
-
-        monaco.languages.typescript.typescriptDefaults.addExtraLib(
-            `
+  return fetch(
+    ('local' in URL_PARAMS
+      ? SCRIPT_URLS.localEChartsDir
+      : SCRIPT_URLS.echartsDir) + '/types/dist/echarts.d.ts',
+    {
+      mode: 'cors'
+    }
+  )
+    .then((response) => response.text())
+    .then((code) => {
+      // validation settings
+      monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
+        noSemanticValidation: false,
+        noSyntaxValidation: false
+      });
+
+      // compiler options
+      monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
+        target: monaco.languages.typescript.ScriptTarget.ES6,
+        allowNonTsExtensions: true,
+        noResolve: false
+      });
+
+      // console.log('file:///node_modules/@types/' + res[i].path);
+      monaco.languages.typescript.typescriptDefaults.addExtraLib(
+        code,
+        // https://github.com/microsoft/monaco-editor/issues/667#issuecomment-468164794
+        'file:///node_modules/@types/echarts/echarts.d.ts'
+      );
+
+      monaco.languages.typescript.typescriptDefaults.addExtraLib(
+        `
 import * as echarts from './echarts';
 // Export for UMD module.
 export as namespace echarts
 export = echarts;`,
-            // https://github.com/microsoft/monaco-editor/issues/667#issuecomment-468164794
-            'file:///node_modules/@types/echarts/index.d.ts'
-        );
+        // https://github.com/microsoft/monaco-editor/issues/667#issuecomment-468164794
+        'file:///node_modules/@types/echarts/index.d.ts'
+      );
 
-        monaco.languages.typescript.typescriptDefaults.addExtraLib(
-`import * as echarts from 'echarts';
+      monaco.languages.typescript.typescriptDefaults.addExtraLib(
+        `import * as echarts from 'echarts';
 // Declare to global namespace.
 declare global {
     const ROOT_PATH: string
@@ -76,117 +79,115 @@ declare global {
     const echarts: typeof echarts
 }
 `,
-            'file:///example.d.ts'
-        );
-        return;
+        'file:///example.d.ts'
+      );
+      return;
     });
 }
 
 function ensureMonacoAndTsTransformer() {
-    function loadMonaco() {
-        if (typeof monaco === 'undefined') {
-            return loadScriptsAsync([
-                SCRIPT_URLS.monacoDir + '/loader.js',
-                // Prebuilt TS transformer with surcrase
-                store.cdnRoot + '/js/example-transform-ts-bundle.js'
-            ]).then(function () {
-                window.require.config({ paths: { 'vs': SCRIPT_URLS.monacoDir }});
-                return new Promise(resolve => {
-                    window.require([
-                        'vs/editor/editor.main'
-                    ], function () {
-                        loadTypes().then(() => {
-                            resolve();
-                        });
-                    })
-                });
-            })
-        }
-        return Promise.resolve();
+  function loadMonaco() {
+    if (typeof monaco === 'undefined') {
+      return loadScriptsAsync([
+        SCRIPT_URLS.monacoDir + '/loader.js',
+        // Prebuilt TS transformer with surcrase
+        store.cdnRoot + '/js/example-transform-ts-bundle.js'
+      ]).then(function () {
+        window.require.config({ paths: { vs: SCRIPT_URLS.monacoDir } });
+        return new Promise((resolve) => {
+          window.require(['vs/editor/editor.main'], function () {
+            loadTypes().then(() => {
+              resolve();
+            });
+          });
+        });
+      });
     }
+    return Promise.resolve();
+  }
 
-    // Must load echarts before monaco. Or the AMD loader will affect loading of echarts.
-    return ensureECharts().then(loadMonaco);
+  // Must load echarts before monaco. Or the AMD loader will affect loading of echarts.
+  return ensureECharts().then(loadMonaco);
 }
 
 export default {
+  props: ['initialCode'],
 
-    props: ['initialCode'],
-
-    data() {
-        return {
-            shared: store,
-            loading: false
-        }
-    },
-
-    mounted() {
-        this.loading = true;
-        ensureMonacoAndTsTransformer().then(() => {
-            this.loading = false;
-            const model = monaco.editor.createModel(
-                this.initialCode || '',
-                'typescript',
-                // Should also be a file path so it can resolve the lib.
-                monaco.Uri.parse('file:///main.ts')
-            );
-            const editor = monaco.editor.create(this.$el, {
-                model,
-                fontFamily: `'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace`,
-                minimap: {
-                    enabled: false
-                },
-                automaticLayout: true
-            });
+  data() {
+    return {
+      shared: store,
+      loading: false
+    };
+  },
+
+  mounted() {
+    this.loading = true;
+    ensureMonacoAndTsTransformer().then(() => {
+      this.loading = false;
+      const model = monaco.editor.createModel(
+        this.initialCode || '',
+        'typescript',
+        // Should also be a file path so it can resolve the lib.
+        monaco.Uri.parse('file:///main.ts')
+      );
+      const editor = monaco.editor.create(this.$el, {
+        model,
+        fontFamily: `'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace`,
+        minimap: {
+          enabled: false
+        },
+        automaticLayout: true
+      });
+
+      this._editor = editor;
+
+      if (this.initialCode) {
+        store.sourceCode = this.initialCode;
+        store.runCode = echartsExampleTransformTs(store.sourceCode);
+      }
+      editor.onDidChangeModelContent(() => {
+        store.sourceCode = editor.getValue();
+        store.runCode = echartsExampleTransformTs(store.sourceCode);
+      });
+    });
+  },
 
-            this._editor = editor;
+  destroyed() {
+    if (this._editor) {
+      this._editor.getModel().dispose();
+      this._editor.dispose();
+    }
+  },
 
-            if (this.initialCode) {
-                store.sourceCode = this.initialCode;
-                store.runCode = echartsExampleTransformTs(store.sourceCode);
-            }
-            editor.onDidChangeModelContent(() => {
-                store.sourceCode = editor.getValue();
-                store.runCode = echartsExampleTransformTs(store.sourceCode);
-            });
-        });
-    },
-
-    destroyed() {
-        if (this._editor) {
-            this._editor.getModel().dispose();
-            this._editor.dispose();
-        }
-    },
-
-    methods: {
-        setInitialCode(code) {
-            if (this._editor && code) {
-                this._editor.setValue(code || '');
-            }
-        }
-    },
+  methods: {
+    setInitialCode(code) {
+      if (this._editor && code) {
+        this._editor.setValue(code || '');
+      }
+    }
+  },
 
-    watch: {
-        initialCode(newVal) {
-            this.setInitialCode(newVal);
-        }
+  watch: {
+    initialCode(newVal) {
+      this.setInitialCode(newVal);
     }
-}
+  }
+};
 </script>
 
 <style lang="scss">
 .monaco-editor-main {
-    font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
-    font-size: 12px;
-    padding: 0;
-    overflow-y: hidden;
-    // height: 100%;
-    // Fix safari
-    position: absolute;
-    left: 0;
-    top: 0;
-    bottom: 0;
-    right: 0;
+  font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas',
+    monospace;
+  font-size: 12px;
+  padding: 0;
+  overflow-y: hidden;
+  // height: 100%;
+  // Fix safari
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  right: 0;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/editor/Editor.vue b/src/editor/Editor.vue
index fe5532d..284487c 100644
--- a/src/editor/Editor.vue
+++ b/src/editor/Editor.vue
@@ -1,236 +1,264 @@
 <template>
-<div id="main-container">
-    <div id="editor-left-container" :style="{width: leftContainerSize + '%'}" v-if="!shared.isMobile">
-        <el-tabs v-model="currentTab" type="border-card">
-            <el-tab-pane :label="$t('editor.tabEditor')" name="code-editor">
-                <el-container>
-                    <el-header id="editor-control-panel">
-                        <div id="code-info">
-                            <template v-if="shared.editorStatus.message">
-                                <span class="code-info-time">{{currentTime}}</span>
-                                <span :class="'code-info-type-' + shared.editorStatus.type">{{shared.editorStatus.message}}</span>
-                            </template>
-                        </div>
-                        <div class="editor-controls">
-                            <!-- <el-switch v-model="shared.typeCheck"
+  <div id="main-container">
+    <div
+      id="editor-left-container"
+      :style="{ width: leftContainerSize + '%' }"
+      v-if="!shared.isMobile"
+    >
+      <el-tabs v-model="currentTab" type="border-card">
+        <el-tab-pane :label="$t('editor.tabEditor')" name="code-editor">
+          <el-container>
+            <el-header id="editor-control-panel">
+              <div id="code-info">
+                <template v-if="shared.editorStatus.message">
+                  <span class="code-info-time">{{ currentTime }}</span>
+                  <span :class="'code-info-type-' + shared.editorStatus.type">{{
+                    shared.editorStatus.message
+                  }}</span>
+                </template>
+              </div>
+              <div class="editor-controls">
+                <!-- <el-switch v-model="shared.typeCheck"
                                 :active-text="$t('editor.monacoMode')"
                                 :inactive-text="''"
                             ></el-switch> -->
-                            <a href="javascript:;" class='btn btn-default btn-sm' @click='disposeAndRun'>{{$t('editor.run')}}</a>
-                        </div>
-                    </el-header>
-                    <el-main>
-                        <CodeMonaco v-if="shared.typeCheck" id="code-panel" :initialCode="initialCode"></CodeMonaco>
-                        <CodeAce v-else id="code-panel" :initialCode="initialCode"></CodeAce>
-                    </el-main>
-                </el-container>
-            </el-tab-pane>
-
-            <el-tab-pane :label="$t('editor.tabFullCodePreview')" name="full-code" :lazy="true">
-                <el-container style="width: 100%; height: 100%">
-                    <el-header id="full-code-generate-config">
-                        <span class="full-code-generate-config-label">
-                            <!-- <i class="el-icon-setting"></i> 配置 -->
-                        </span>
-                        <el-switch
-                            class="enable-decal"
-                            v-model="fullCodeConfig.minimal"
-                            :active-text="$t('editor.minimalBundle')"
-                            :inactive-text="''">
-                        </el-switch>
-                        <el-switch
-                            class="enable-decal"
-                            v-model="fullCodeConfig.esm"
-                            active-text="ES Modules"
-                            :inactive-text="''">
-                        </el-switch>
-                    </el-header>
-                    <el-main>
-                        <FullCodePreview :code="fullCode"></FullCodePreview>
-                    </el-main>
-                </el-container>
-            </el-tab-pane>
-            <el-tab-pane :label="$t('editor.tabOptionPreview')" name="full-option">
-                <div id="option-outline"></div>
-            </el-tab-pane>
-        </el-tabs>
+                <a
+                  href="javascript:;"
+                  class="btn btn-default btn-sm"
+                  @click="disposeAndRun"
+                  >{{ $t('editor.run') }}</a
+                >
+              </div>
+            </el-header>
+            <el-main>
+              <CodeMonaco
+                v-if="shared.typeCheck"
+                id="code-panel"
+                :initialCode="initialCode"
+              ></CodeMonaco>
+              <CodeAce
+                v-else
+                id="code-panel"
+                :initialCode="initialCode"
+              ></CodeAce>
+            </el-main>
+          </el-container>
+        </el-tab-pane>
+
+        <el-tab-pane
+          :label="$t('editor.tabFullCodePreview')"
+          name="full-code"
+          :lazy="true"
+        >
+          <el-container style="width: 100%; height: 100%">
+            <el-header id="full-code-generate-config">
+              <span class="full-code-generate-config-label">
+                <!-- <i class="el-icon-setting"></i> 配置 -->
+              </span>
+              <el-switch
+                class="enable-decal"
+                v-model="fullCodeConfig.minimal"
+                :active-text="$t('editor.minimalBundle')"
+                :inactive-text="''"
+              >
+              </el-switch>
+              <el-switch
+                class="enable-decal"
+                v-model="fullCodeConfig.esm"
+                active-text="ES Modules"
+                :inactive-text="''"
+              >
+              </el-switch>
+            </el-header>
+            <el-main>
+              <FullCodePreview :code="fullCode"></FullCodePreview>
+            </el-main>
+          </el-container>
+        </el-tab-pane>
+        <el-tab-pane :label="$t('editor.tabOptionPreview')" name="full-option">
+          <div id="option-outline"></div>
+        </el-tab-pane>
+      </el-tabs>
     </div>
-    <div class="handler" id="h-handler" @mousedown="onSplitterDragStart" :style="{left: leftContainerSize + '%'}" v-if="!shared.isMobile"></div>
-    <Preview :inEditor="true" class="right-container" ref="preview" :style="{
-        width: (100 - leftContainerSize) + '%',
+    <div
+      class="handler"
+      id="h-handler"
+      @mousedown="onSplitterDragStart"
+      :style="{ left: leftContainerSize + '%' }"
+      v-if="!shared.isMobile"
+    ></div>
+    <Preview
+      :inEditor="true"
+      class="right-container"
+      ref="preview"
+      :style="{
+        width: 100 - leftContainerSize + '%',
         left: leftContainerSize + '%'
-    }"></Preview>
-</div>
+      }"
+    ></Preview>
+  </div>
 </template>
 
 <script>
-
 import CodeAce from './CodeAce.vue';
 import CodeMonaco from './CodeMonaco.vue';
 import FullCodePreview from './FullCodePreview.vue';
 import Preview from './Preview.vue';
-import {store, loadExampleCode, parseSourceCode} from '../common/store';
-import {collectDeps, buildExampleCode} from '../../common/buildCode';
-import { mount } from "@lang/object-visualizer";
+import { store, loadExampleCode, parseSourceCode } from '../common/store';
+import { collectDeps, buildExampleCode } from '../../common/buildCode';
+import { mount } from '@lang/object-visualizer';
 
 import './object-visualizer.css';
 
 export default {
-    components: {
-        CodeAce,
-        CodeMonaco,
-        FullCodePreview,
-        Preview
-    },
-
-    data() {
-        return {
-            mousedown: false,
-            leftContainerSize: 40,
-            mobileMode: false,
-            shared: store,
-            initialCode: '',
-
-            currentTab: 'code-editor',
+  components: {
+    CodeAce,
+    CodeMonaco,
+    FullCodePreview,
+    Preview
+  },
+
+  data() {
+    return {
+      mousedown: false,
+      leftContainerSize: 40,
+      mobileMode: false,
+      shared: store,
+      initialCode: '',
+
+      currentTab: 'code-editor',
+
+      fullCode: '',
+
+      fullCodeConfig: {
+        mimimal: false,
+        esm: true,
+        node: false // If is in node
+      }
+    };
+  },
+
+  computed: {
+    currentTime() {
+      // Update time when message updated.
+      const message = this.shared.message;
+
+      const time = new Date();
+      const digits = [time.getHours(), time.getMinutes(), time.getSeconds()];
+      let timeStr = '';
+      for (let i = 0, len = digits.length; i < len; ++i) {
+        timeStr += (digits[i] < 10 ? '0' : '') + digits[i];
+        if (i < len - 1) {
+          timeStr += ':';
+        }
+      }
+      return timeStr;
+    }
+  },
+
+  mounted() {
+    if (store.isMobile) {
+      this.leftContainerSize = 0;
+      loadExampleCode().then((code) => {
+        // No editor available. Set to runCode directly.
+        store.runCode = parseSourceCode(code);
+      });
+    } else {
+      loadExampleCode().then((code) => {
+        // Only set the code in editor. editor will sync to the store.
+        this.initialCode = parseSourceCode(code);
+      });
+
+      window.addEventListener('mousemove', (e) => {
+        if (this.mousedown) {
+          let percentage = e.clientX / window.innerWidth;
+          percentage = Math.min(0.9, Math.max(0.1, percentage));
+          this.leftContainerSize = percentage * 100;
+        }
+      });
 
-            fullCode: '',
+      window.addEventListener('mouseup', (e) => {
+        this.mousedown = false;
+      });
+    }
+  },
 
-            fullCodeConfig: {
-                mimimal: false,
-                esm: true,
-                node: false // If is in node
-            }
-        };
+  methods: {
+    onSplitterDragStart() {
+      this.mousedown = true;
     },
-
-    computed: {
-        currentTime() {
-            // Update time when message updated.
-            const message = this.shared.message;
-
-            const time = new Date();
-            const digits = [time.getHours(), time.getMinutes(), time.getSeconds()];
-            let timeStr = '';
-            for (let i = 0, len = digits.length; i < len; ++i) {
-                timeStr += (digits[i] < 10 ? '0' : '') + digits[i];
-                if (i < len - 1) {
-                    timeStr += ':';
-                }
-            }
-            return timeStr;
-        }
+    disposeAndRun() {
+      this.$refs.preview.refreshAll();
     },
-
-    mounted() {
-
-        if (store.isMobile) {
-            this.leftContainerSize = 0;
-            loadExampleCode().then(code => {
-                // No editor available. Set to runCode directly.
-                store.runCode = parseSourceCode(code);
-            });
-        }
-        else {
-            loadExampleCode().then(code => {
-                // Only set the code in editor. editor will sync to the store.
-                this.initialCode = parseSourceCode(code);
-            });
-
-            window.addEventListener('mousemove', (e) => {
-                if (this.mousedown) {
-                    let percentage = e.clientX / window.innerWidth;
-                    percentage = Math.min(0.9, Math.max(0.1, percentage));
-                    this.leftContainerSize = percentage * 100;
-                }
-            });
-
-            window.addEventListener('mouseup', (e) => {
-                this.mousedown = false;
-            });
-        }
+    updateFullCode() {
+      const option = this.$refs.preview.getOption();
+      if (!option) {
+        return;
+      }
+      const deps = collectDeps(option);
+      deps.push(store.renderer === 'svg' ? 'SVGRenderer' : 'CanvasRenderer');
+      this.fullCode = buildExampleCode(store.sourceCode, deps, {
+        minimal: this.fullCodeConfig.minimal,
+        ts: false,
+        esm: this.fullCodeConfig.esm,
+        // legacy: true,
+        theme: store.darkMode ? 'dark' : '',
+        ROOT_PATH: store.cdnRoot
+      });
     },
-
-    methods: {
-        onSplitterDragStart() {
-            this.mousedown = true;
-        },
-        disposeAndRun() {
-            this.$refs.preview.refreshAll();
-        },
-        updateFullCode() {
-            const option = this.$refs.preview.getOption();
-            if (!option) {
-                return;
-            }
-            const deps = collectDeps(option);
-            deps.push(store.renderer === 'svg' ? 'SVGRenderer' : 'CanvasRenderer');
-            this.fullCode = buildExampleCode(store.sourceCode, deps, {
-                minimal: this.fullCodeConfig.minimal,
-                ts: false,
-                esm: this.fullCodeConfig.esm,
-                // legacy: true,
-                theme: store.darkMode ? 'dark' : '',
-                ROOT_PATH: store.cdnRoot
-            });
-        },
-        updateOptionOutline() {
-            const option = Object.freeze(this.$refs.preview.getOption());
-            if (!option) {
-                return;
+    updateOptionOutline() {
+      const option = Object.freeze(this.$refs.preview.getOption());
+      if (!option) {
+        return;
+      }
+      mount(option, this.$el.querySelector('#option-outline'), {
+        getKeys(object, path) {
+          return Object.keys(object).filter((key) => {
+            if (Array.isArray(object[key]) && !object[key].length) {
+              return false;
             }
-            mount(
-                option,
-                this.$el.querySelector('#option-outline'),
-                {
-                    getKeys(object, path) {
-                        return Object.keys(object).filter(key => {
-                            if (Array.isArray(object[key]) && !object[key].length) {
-                                return false;
-                            }
-                            return true;
-                        });
-                    },
-                    expandOnCreatedAndUpdated(path) {
-                        return path.length === 0
-                            || (path[0] === 'series' && path.length <= 1);
-                    },
-                }
-            );
+            return true;
+          });
         },
-        updateTabContent(tab) {
-            if (tab === 'full-code') {
-                this.updateFullCode();
-            }
-            else if (tab === 'full-option') {
-                this.updateOptionOutline();
-            }
+        expandOnCreatedAndUpdated(path) {
+          return (
+            path.length === 0 || (path[0] === 'series' && path.length <= 1)
+          );
         }
+      });
     },
+    updateTabContent(tab) {
+      if (tab === 'full-code') {
+        this.updateFullCode();
+      } else if (tab === 'full-option') {
+        this.updateOptionOutline();
+      }
+    }
+  },
 
-    watch: {
-        'shared.typeCheck'(enableTypeCheck) {
-            // Update initialCode to avoid code changed when switching editor
-            this.initialCode = store.sourceCode;
-            this.updateFullCode();
-        },
-        'currentTab'(tab) {
-            this.updateTabContent(tab);
-        },
-        'shared.runHash'() {
-            this.updateTabContent(this.currentTab);
-        },
-        fullCodeConfig: {
-            deep: true,
-            handler() {
-                this.updateFullCode();
-            }
-        }
+  watch: {
+    'shared.typeCheck'(enableTypeCheck) {
+      // Update initialCode to avoid code changed when switching editor
+      this.initialCode = store.sourceCode;
+      this.updateFullCode();
+    },
+    currentTab(tab) {
+      this.updateTabContent(tab);
+    },
+    'shared.runHash'() {
+      this.updateTabContent(this.currentTab);
+    },
+    fullCodeConfig: {
+      deep: true,
+      handler() {
+        this.updateFullCode();
+      }
     }
-}
+  }
+};
 </script>
 
 <style lang="scss">
-
 @import '../style/color.scss';
 
 $code-info-height: 25px;
@@ -239,215 +267,215 @@ $pd-basic: 10px;
 $handler-width: 5px;
 
 #main-container {
-    .handler {
-        position: absolute;
-        left: 50%;
-
-        top: 0;
-        bottom: 0;
-        width: $handler-width;
-
-        cursor: col-resize;
-        z-index: 100;
-        background-color: transparent;
-        border-left: 1px solid #ececec;
-        // border-right: 1px solid $clr-border;
-    }
+  .handler {
+    position: absolute;
+    left: 50%;
 
+    top: 0;
+    bottom: 0;
+    width: $handler-width;
+
+    cursor: col-resize;
+    z-index: 100;
+    background-color: transparent;
+    border-left: 1px solid #ececec;
+    // border-right: 1px solid $clr-border;
+  }
 }
 
 #editor-left-container {
-    position: absolute;
-    left: 0;
-    bottom: 0;
-    top: 0;
+  position: absolute;
+  left: 0;
+  bottom: 0;
+  top: 0;
 
-    width: 50%;
+  width: 50%;
 
-    .el-tab-pane {
-        height: 100%;
-
-        .el-container {
-            width: 100%;
-            height: 100%;
-        }
-
-        .el-header {
-            height: $control-panel-height!important;
-            position: relative;
-            z-index: 10;
-            padding: 0;
-        }
-        .el-main {
-            padding: 0;
-            position: relative;
-
-            ::-webkit-scrollbar {
-                height:8px;
-                width:8px;
-                transition:all 0.3s ease-in-out;
-                border-radius:2px;
-            }
-
-            ::-webkit-scrollbar-button {
-                display:none;
-            }
-
-            ::-webkit-scrollbar-thumb {
-                width:8px;
-                min-height:15px;
-                background:rgba(50, 50, 50, 0.6) !important;
-                transition:all 0.3s ease-in-out;border-radius:2px;
-            }
-
-            ::-webkit-scrollbar-thumb:hover {
-                background:rgba(0, 0, 0, 0.5) !important;
-            }
-        }
-    }
+  .el-tab-pane {
+    height: 100%;
 
-    .el-tabs {
-        box-shadow: none;
+    .el-container {
+      width: 100%;
+      height: 100%;
     }
 
-    .el-tabs--border-card>.el-tabs__header {
-        border-bottom: none;
+    .el-header {
+      height: $control-panel-height !important;
+      position: relative;
+      z-index: 10;
+      padding: 0;
     }
-
-    .el-tabs__content {
-        position: absolute;
-        top: 34px;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        padding: 0;
+    .el-main {
+      padding: 0;
+      position: relative;
+
+      ::-webkit-scrollbar {
+        height: 8px;
+        width: 8px;
+        transition: all 0.3s ease-in-out;
+        border-radius: 2px;
+      }
+
+      ::-webkit-scrollbar-button {
+        display: none;
+      }
+
+      ::-webkit-scrollbar-thumb {
+        width: 8px;
+        min-height: 15px;
+        background: rgba(50, 50, 50, 0.6) !important;
+        transition: all 0.3s ease-in-out;
+        border-radius: 2px;
+      }
+
+      ::-webkit-scrollbar-thumb:hover {
+        background: rgba(0, 0, 0, 0.5) !important;
+      }
     }
+  }
 
-    .el-tabs__item {
-        height: 34px;
-        line-height: 34px;
-    }
-}
+  .el-tabs {
+    box-shadow: none;
+  }
 
-#editor-control-panel, #full-code-generate-config {
-    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
-}
+  .el-tabs--border-card > .el-tabs__header {
+    border-bottom: none;
+  }
 
-#option-outline {
-    // height: 100%;
-    // Fix safari
+  .el-tabs__content {
     position: absolute;
+    top: 34px;
     left: 0;
-    top: 0;
-    bottom: 0;
     right: 0;
+    bottom: 0;
+    padding: 0;
+  }
 
-    font-size: 13px;
-
-    font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
+  .el-tabs__item {
+    height: 34px;
+    line-height: 34px;
+  }
 }
 
+#editor-control-panel,
 #full-code-generate-config {
-    .full-code-generate-config-label {
-        height: $control-panel-height;
-        line-height: $control-panel-height;
-        vertical-align: middle;
-        margin: 0 0 0 20px;
-    }
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
 
-    .el-switch {
-        margin-right: 10px;
-    }
+#option-outline {
+  // height: 100%;
+  // Fix safari
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  right: 0;
+
+  font-size: 13px;
+
+  font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas',
+    monospace;
+}
 
-    .el-switch__label {
-        margin-left: 8px;
-        margin-top: -2px;
-    }
-    .el-switch__label * {
-        font-size: 12px;
-    }
+#full-code-generate-config {
+  .full-code-generate-config-label {
+    height: $control-panel-height;
+    line-height: $control-panel-height;
+    vertical-align: middle;
+    margin: 0 0 0 20px;
+  }
+
+  .el-switch {
+    margin-right: 10px;
+  }
+
+  .el-switch__label {
+    margin-left: 8px;
+    margin-top: -2px;
+  }
+  .el-switch__label * {
+    font-size: 12px;
+  }
 }
 
 #editor-control-panel {
-    .setting-panel {
-        display: inline-block;
+  .setting-panel {
+    display: inline-block;
 
-        .btn-group + .btn-group {
-            margin-left: $pd-basic;
-        }
+    .btn-group + .btn-group {
+      margin-left: $pd-basic;
     }
+  }
 
-    .editor-controls  {
-        float: right;
+  .editor-controls {
+    float: right;
 
-        .el-switch__label {
-            margin-top: -3px;
-        }
-        .el-switch__label * {
-            font-size: 12px;
-        }
-
-        .btn {
-            color: #FFF;
-            border-radius: 0;
-            background-color: #409eff;
-            margin-left: $pd-basic;
-            border: none;
-            height: 30px;
-            width: 50px;
-        }
-        .btn:hover {
-            background-color: lighten($color: #409eff, $amount: 5);
-        }
+    .el-switch__label {
+      margin-top: -3px;
     }
-}
-
-#code-info {
-    position: absolute;
-    bottom: 0;
-    overflow: hidden;
-
-    height: $control-panel-height;
-    line-height: $control-panel-height;
-    padding: 0px 10px;
-
-    // border-top: 1px solid $clr-border;
-    font-size: 0.9rem;
-
-    .code-info-time {
-        color: $clr-text;
-        display: inline-block;
-        margin-right: 10px;
-        font-size: 12px;
+    .el-switch__label * {
+      font-size: 12px;
     }
 
-    .code-info-type-info {
-        color: $clr-text;
-        font-size: 12px;
+    .btn {
+      color: #fff;
+      border-radius: 0;
+      background-color: #409eff;
+      margin-left: $pd-basic;
+      border: none;
+      height: 30px;
+      width: 50px;
     }
-
-    .code-info-type-warn {
-        color: $clr-warn;
+    .btn:hover {
+      background-color: lighten($color: #409eff, $amount: 5);
     }
+  }
+}
 
-    .code-info-type-error {
-        color: $clr-error;
-    }
+#code-info {
+  position: absolute;
+  bottom: 0;
+  overflow: hidden;
+
+  height: $control-panel-height;
+  line-height: $control-panel-height;
+  padding: 0px 10px;
+
+  // border-top: 1px solid $clr-border;
+  font-size: 0.9rem;
+
+  .code-info-time {
+    color: $clr-text;
+    display: inline-block;
+    margin-right: 10px;
+    font-size: 12px;
+  }
+
+  .code-info-type-info {
+    color: $clr-text;
+    font-size: 12px;
+  }
+
+  .code-info-type-warn {
+    color: $clr-warn;
+  }
+
+  .code-info-type-error {
+    color: $clr-error;
+  }
 }
 
 .right-container {
-    position: absolute;
-    right: 0;
+  position: absolute;
+  right: 0;
 
-    width: 50%;
-    height: 100%;
-    padding: 0;
-    padding-left: $handler-width;
-    border: none;
-    z-index: 30;
+  width: 50%;
+  height: 100%;
+  padding: 0;
+  padding-left: $handler-width;
+  border: none;
+  z-index: 30;
 
-    background: $clr-bg;
+  background: $clr-bg;
 }
-
-
-</style>
\ No newline at end of file
+</style>
diff --git a/src/editor/FullCodePreview.vue b/src/editor/FullCodePreview.vue
index e2f1c3f..19fce17 100644
--- a/src/editor/FullCodePreview.vue
+++ b/src/editor/FullCodePreview.vue
@@ -1,94 +1,91 @@
 <template>
-<div class="full-code-preview" v-loading="loading"></div>
+  <div class="full-code-preview" v-loading="loading"></div>
 </template>
 
 <script>
-
-import {loadScriptsAsync} from '../common/helper';
-import {store} from '../common/store';
-import {SCRIPT_URLS} from '../common/config';
+import { loadScriptsAsync } from '../common/helper';
+import { store } from '../common/store';
+import { SCRIPT_URLS } from '../common/config';
 
 function ensureACE() {
-    if (typeof ace === 'undefined') {
-        return loadScriptsAsync([
-            SCRIPT_URLS.aceDir + '/ace.js'
-        ]);
-    }
-    return Promise.resolve();
+  if (typeof ace === 'undefined') {
+    return loadScriptsAsync([SCRIPT_URLS.aceDir + '/ace.js']);
+  }
+  return Promise.resolve();
 }
 
 export default {
+  props: ['code'],
 
-    props: ['code'],
-
-    data() {
-        return {
-            shared: store,
-            loading: false
-        }
-    },
+  data() {
+    return {
+      shared: store,
+      loading: false
+    };
+  },
 
-    mounted() {
-        this.loading = true;
-        ensureACE().then(() => {
-            this.loading = false;
-            const editor = ace.edit(this.$el);
-            editor.getSession().setMode('ace/mode/javascript');
-            // https://stackoverflow.com/questions/32806060/is-there-a-programmatic-way-to-hide-the-cursor-in-ace-editor
-            editor.setOptions({
-                readOnly: true,
-                showLineNumbers: false,
-                showFoldWidgets: false,
-                highlightActiveLine: false,
-                highlightGutterLine: false
-            });
-            // editor.renderer.setShowGutter(false);
-            editor.renderer.$cursorLayer.element.style.display = 'none';
+  mounted() {
+    this.loading = true;
+    ensureACE().then(() => {
+      this.loading = false;
+      const editor = ace.edit(this.$el);
+      editor.getSession().setMode('ace/mode/javascript');
+      // https://stackoverflow.com/questions/32806060/is-there-a-programmatic-way-to-hide-the-cursor-in-ace-editor
+      editor.setOptions({
+        readOnly: true,
+        showLineNumbers: false,
+        showFoldWidgets: false,
+        highlightActiveLine: false,
+        highlightGutterLine: false
+      });
+      // editor.renderer.setShowGutter(false);
+      editor.renderer.$cursorLayer.element.style.display = 'none';
 
-            this._editor = editor;
+      this._editor = editor;
 
-            this.setCode(this.code);
+      this.setCode(this.code);
+    });
+  },
 
+  methods: {
+    setCode(code) {
+      if (this._editor) {
+        this._editor.setValue(code);
+        this._editor.selection.setSelectionRange({
+          start: {
+            row: 1,
+            column: 4
+          },
+          end: {
+            row: 1,
+            column: 4
+          }
         });
-    },
-
-    methods: {
-        setCode(code) {
-            if (this._editor) {
-                this._editor.setValue(code);
-                this._editor.selection.setSelectionRange({
-                    start: {
-                        row:1,
-                        column: 4
-                    }, end: {
-                        row:1,
-                        column: 4
-                    }
-                });
-            }
-        }
-    },
+      }
+    }
+  },
 
-    watch: {
-        code(newVal) {
-            this.setCode(newVal);
-        }
+  watch: {
+    code(newVal) {
+      this.setCode(newVal);
     }
-}
+  }
+};
 </script>
 
 <style lang="scss">
 .full-code-preview {
-    font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
-    font-size: 12px;
-    line-height: 18px;
-    // height: 100%;
+  font-family: 'Source Code Pro', 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas',
+    monospace;
+  font-size: 12px;
+  line-height: 18px;
+  // height: 100%;
 
-    // Fix safari
-    position: absolute;
-    left: 0;
-    top: 0;
-    bottom: 0;
-    right: 0;
+  // Fix safari
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  right: 0;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/editor/Preview.vue b/src/editor/Preview.vue
index 7903283..3d27e1d 100644
--- a/src/editor/Preview.vue
+++ b/src/editor/Preview.vue
@@ -1,424 +1,446 @@
 <template>
-<div :class="[(inEditor && !shared.isMobile) ? '' : 'full']">
-    <div v-loading="loading"
-        class="right-panel"
-        id="chart-panel"
-        :style="{background: backgroundColor}"
+  <div :class="[inEditor && !shared.isMobile ? '' : 'full']">
+    <div
+      v-loading="loading"
+      class="right-panel"
+      id="chart-panel"
+      :style="{ background: backgroundColor }"
     >
-        <div class="chart-container"></div>
+      <div class="chart-container"></div>
     </div>
     <div id="tool-panel">
-        <div class="left-panel">
-            <el-switch
-                class="dark-mode"
-                v-model="shared.darkMode"
-                active-color="#181432"
-                :active-text="$t('editor.darkMode')"
-                :inactive-text="''">
-            </el-switch>
-            <el-switch
-                v-if="!isGL"
-
-                class="enable-decal"
-                v-model="shared.enableDecal"
-                :active-text="$t('editor.enableDecal')"
-                :inactive-text="''">
-            </el-switch>
-
-            <el-popover
-                placement="bottom"
-                width="450"
-                trigger="click"
-                style="margin-top:3px;"
-
-                v-if="!isGL"
-            >
-                <div class="render-config-container">
-                    <el-row :gutter="2" type="flex" align="middle">
-                        <el-col :span="12">
-                            <label class="tool-label">{{ $t('editor.renderer') }}</label>
-                            <el-radio-group v-model="shared.renderer" size="mini" style="text-transform: uppercase">
-                                <el-radio-button label="svg"></el-radio-button>
-                                <el-radio-button label="canvas"></el-radio-button>
-                            </el-radio-group>
-                        </el-col>
-                        <el-col :span="12">
-                            <el-switch
-                                v-if="shared.renderer==='canvas'"
-                                v-model="shared.useDirtyRect"
-                                :active-text="$t('editor.useDirtyRect')"
-                                :inactive-text="''">
-                            </el-switch>
-                        </el-col>
-                    </el-row>
-                </div>
-                <span class="render-config-trigger" slot="reference"><i class="el-icon-setting el-icon--left"></i>{{$t('editor.renderCfgTitle')}}</span>
-            </el-popover>
-        </div>
-        <template v-if="inEditor">
-            <button v-if="!shared.isMobile" class="download btn btn-sm" @click="downloadExample">{{ $t('editor.download') }}</button>
-            <a class="screenshot" @click="screenshot" target="_blank"><i class="el-icon-camera-solid"></i></a>
-        </template>
-        <a :href="editLink" target="_blank" v-else class="edit btn btn-sm">{{ $t('editor.edit') }}</a>
+      <div class="left-panel">
+        <el-switch
+          class="dark-mode"
+          v-model="shared.darkMode"
+          active-color="#181432"
+          :active-text="$t('editor.darkMode')"
+          :inactive-text="''"
+        >
+        </el-switch>
+        <el-switch
+          v-if="!isGL"
+          class="enable-decal"
+          v-model="shared.enableDecal"
+          :active-text="$t('editor.enableDecal')"
+          :inactive-text="''"
+        >
+        </el-switch>
+
+        <el-popover
+          placement="bottom"
+          width="450"
+          trigger="click"
+          style="margin-top: 3px"
+          v-if="!isGL"
+        >
+          <div class="render-config-container">
+            <el-row :gutter="2" type="flex" align="middle">
+              <el-col :span="12">
+                <label class="tool-label">{{ $t('editor.renderer') }}</label>
+                <el-radio-group
+                  v-model="shared.renderer"
+                  size="mini"
+                  style="text-transform: uppercase"
+                >
+                  <el-radio-button label="svg"></el-radio-button>
+                  <el-radio-button label="canvas"></el-radio-button>
+                </el-radio-group>
+              </el-col>
+              <el-col :span="12">
+                <el-switch
+                  v-if="shared.renderer === 'canvas'"
+                  v-model="shared.useDirtyRect"
+                  :active-text="$t('editor.useDirtyRect')"
+                  :inactive-text="''"
+                >
+                </el-switch>
+              </el-col>
+            </el-row>
+          </div>
+          <span class="render-config-trigger" slot="reference"
+            ><i class="el-icon-setting el-icon--left"></i
+            >{{ $t('editor.renderCfgTitle') }}</span
+          >
+        </el-popover>
+      </div>
+      <template v-if="inEditor">
+        <button
+          v-if="!shared.isMobile"
+          class="download btn btn-sm"
+          @click="downloadExample"
+        >
+          {{ $t('editor.download') }}
+        </button>
+        <a class="screenshot" @click="screenshot" target="_blank"
+          ><i class="el-icon-camera-solid"></i
+        ></a>
+      </template>
+      <a :href="editLink" target="_blank" v-else class="edit btn btn-sm">{{
+        $t('editor.edit')
+      }}</a>
     </div>
-</div>
+  </div>
 </template>
 
 <script>
-
-import {store, updateRunHash} from '../common/store';
-import {SCRIPT_URLS, URL_PARAMS} from '../common/config';
-import {loadScriptsAsync} from '../common/helper';
-import {createSandbox} from './sandbox';
+import { store, updateRunHash } from '../common/store';
+import { SCRIPT_URLS, URL_PARAMS } from '../common/config';
+import { loadScriptsAsync } from '../common/helper';
+import { createSandbox } from './sandbox';
 import debounce from 'lodash/debounce';
 import { addListener, removeListener } from 'resize-detector';
 import CHART_LIST from '../data/chart-list-data';
 import CHART_LIST_GL from '../data/chart-list-data-gl';
-import {download} from './downloadExample';
+import { download } from './downloadExample';
 
 function findExample(item) {
-    return URL_PARAMS.c === item.id;
+  return URL_PARAMS.c === item.id;
 }
 const example = CHART_LIST.concat(CHART_LIST_GL).find(findExample);
 const isGL = CHART_LIST_GL.find(findExample);
 
 function addDecalIfNecessary(option) {
-    if (store.enableDecal) {
-        option.aria = option.aria || {};
-        option.aria.decal = option.aria.decal || {};
-        option.aria.decal.show = true;
-        option.aria.show = option.aria.enabled = true;
-    }
+  if (store.enableDecal) {
+    option.aria = option.aria || {};
+    option.aria.decal = option.aria.decal || {};
+    option.aria.decal.show = true;
+    option.aria.show = option.aria.enabled = true;
+  }
 }
 
 export function ensureECharts() {
-    if (typeof echarts === 'undefined') {
-
-        const hasBmap = example && example.tags.indexOf('bmap') >= 0;
-
-        // Code from https://api.map.baidu.com/api?v=2.0&ak=KOmVjPVUAey1G2E8zNhPiuQ6QiEmAwZu
-        if (hasBmap) {
-            window.HOST_TYPE = "2";
-            window.BMap_loadScriptTime = (new Date).getTime();
-        }
+  if (typeof echarts === 'undefined') {
+    const hasBmap = example && example.tags.indexOf('bmap') >= 0;
 
-        return loadScriptsAsync([
-            SCRIPT_URLS.datGUIMinJS,
-            'local' in URL_PARAMS
-                ? SCRIPT_URLS.localEChartsMinJS : SCRIPT_URLS.echartsMinJS,
-            SCRIPT_URLS.echartsDir + '/dist/extension/dataTool.js',
-            'https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/js/world.js',
-            SCRIPT_URLS.echartsStatMinJS,
-            ...URL_PARAMS.gl ? [SCRIPT_URLS.echartsGLMinJS] : [],
-            ...hasBmap ? [
-                'https://api.map.baidu.com/getscript?v=3.0&ak=KOmVjPVUAey1G2E8zNhPiuQ6QiEmAwZu&services=&t=20200327103013',
-                SCRIPT_URLS.echartsDir + '/dist/extension/bmap.js'
-            ] : []
-        ]).then(() => {
-            echarts.registerPreprocessor(addDecalIfNecessary)
-        });
+    // Code from https://api.map.baidu.com/api?v=2.0&ak=KOmVjPVUAey1G2E8zNhPiuQ6QiEmAwZu
+    if (hasBmap) {
+      window.HOST_TYPE = '2';
+      window.BMap_loadScriptTime = new Date().getTime();
     }
-    return Promise.resolve();
-}
 
+    return loadScriptsAsync([
+      SCRIPT_URLS.datGUIMinJS,
+      'local' in URL_PARAMS
+        ? SCRIPT_URLS.localEChartsMinJS
+        : SCRIPT_URLS.echartsMinJS,
+      SCRIPT_URLS.echartsDir + '/dist/extension/dataTool.js',
+      'https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/js/world.js',
+      SCRIPT_URLS.echartsStatMinJS,
+      ...(URL_PARAMS.gl ? [SCRIPT_URLS.echartsGLMinJS] : []),
+      ...(hasBmap
+        ? [
+            'https://api.map.baidu.com/getscript?v=3.0&ak=KOmVjPVUAey1G2E8zNhPiuQ6QiEmAwZu&services=&t=20200327103013',
+            SCRIPT_URLS.echartsDir + '/dist/extension/bmap.js'
+          ]
+        : [])
+    ]).then(() => {
+      echarts.registerPreprocessor(addDecalIfNecessary);
+    });
+  }
+  return Promise.resolve();
+}
 
 function log(text, type) {
-    if (type !== 'warn' && type !== 'error') {
-        type = 'info';
-    }
-    store.editorStatus.message = text;
-    store.editorStatus.type = type;
+  if (type !== 'warn' && type !== 'error') {
+    type = 'info';
+  }
+  store.editorStatus.message = text;
+  store.editorStatus.type = type;
 }
 
-
 function run() {
-
-    if (typeof echarts === 'undefined') {
-        return;
-    }
-    if (!this.sandbox) {
-        this.sandbox = createSandbox((chart) => {
-            const option = chart.getOption();
-            if (typeof option.backgroundColor === 'string' && option.backgroundColor !== 'transparent') {
-                this.backgroundColor = option.backgroundColor;
-            }
-            else {
-                this.backgroundColor = '#fff';
-            }
+  if (typeof echarts === 'undefined') {
+    return;
+  }
+  if (!this.sandbox) {
+    this.sandbox = createSandbox((chart) => {
+      const option = chart.getOption();
+      if (
+        typeof option.backgroundColor === 'string' &&
+        option.backgroundColor !== 'transparent'
+      ) {
+        this.backgroundColor = option.backgroundColor;
+      } else {
+        this.backgroundColor = '#fff';
+      }
+    });
+  }
+
+  try {
+    const updateTime = this.sandbox.run(
+      this.$el.querySelector('.chart-container'),
+      store
+    );
+
+    log(this.$t('editor.chartOK') + updateTime + 'ms');
+
+    // Find the appropriate throttle time
+    const debounceTime = 500;
+    const debounceTimeQuantities = [0, 500, 2000, 5000, 10000];
+    for (let i = debounceTimeQuantities.length - 1; i >= 0; i--) {
+      const quantity = debounceTimeQuantities[i];
+      const preferredDebounceTime = debounceTimeQuantities[i + 1] || 1000000;
+      if (
+        updateTime >= quantity &&
+        this.debouncedTime !== preferredDebounceTime
+      ) {
+        this.debouncedRun = debounce(run, preferredDebounceTime, {
+          trailing: true
         });
+        this.debouncedTime = preferredDebounceTime;
+        break;
+      }
     }
 
-    try {
-        const updateTime = this.sandbox.run(this.$el.querySelector('.chart-container'), store);
-
-        log(this.$t('editor.chartOK') + updateTime + 'ms');
-
-        // Find the appropriate throttle time
-        const debounceTime = 500;
-        const debounceTimeQuantities = [0, 500, 2000, 5000, 10000];
-        for (let i = debounceTimeQuantities.length - 1; i >= 0; i--) {
-            const quantity = debounceTimeQuantities[i];
-            const preferredDebounceTime = debounceTimeQuantities[i + 1] || 1000000;
-            if (updateTime >= quantity && this.debouncedTime !== preferredDebounceTime) {
-                this.debouncedRun = debounce(run, preferredDebounceTime, {
-                    trailing: true
-                });
-                this.debouncedTime = preferredDebounceTime;
-                break;
-            }
-        }
-
-        // Update run hash to let others known chart has been changed.
-        updateRunHash();
-
-    }
-    catch (e) {
-        log(this.$t('editor.errorInEditor'), 'error');
-        console.error(e);
-    }
+    // Update run hash to let others known chart has been changed.
+    updateRunHash();
+  } catch (e) {
+    log(this.$t('editor.errorInEditor'), 'error');
+    console.error(e);
+  }
 }
 
-
 export default {
-
-    props: ['inEditor'],
-
-    data() {
-        return {
-            shared: store,
-            debouncedTime: undefined,
-            backgroundColor: '',
-            autoRun: true,
-            loading: false,
-
-            isGL
+  props: ['inEditor'],
+
+  data() {
+    return {
+      shared: store,
+      debouncedTime: undefined,
+      backgroundColor: '',
+      autoRun: true,
+      loading: false,
+
+      isGL
+    };
+  },
+
+  mounted() {
+    this.loading = true;
+    ensureECharts().then(() => {
+      this.loading = false;
+      if (store.runCode) {
+        this.run();
+      }
+    });
+
+    addListener(this.$el, () => {
+      if (this.sandbox) {
+        this.sandbox.resize();
+      }
+    });
+  },
+
+  computed: {
+    editLink() {
+      const params = ['c=' + URL_PARAMS.c];
+      if (URL_PARAMS.theme) {
+        params.push(['theme=' + URL_PARAMS.theme]);
+      }
+      if (URL_PARAMS.gl) {
+        params.push(['gl=' + URL_PARAMS.gl]);
+      }
+      return './editor.html?' + params.join('&');
+    }
+  },
+
+  watch: {
+    'shared.runCode'(val) {
+      if (this.autoRun || !this.sandbox) {
+        if (!this.debouncedRun) {
+          // First run
+          this.run();
+        } else {
+          this.debouncedRun();
         }
+      }
     },
-
-    mounted() {
-        this.loading = true;
-        ensureECharts().then(() => {
-            this.loading = false;
-            if (store.runCode) {
-                this.run();
-            }
-        });
-
-        addListener(this.$el, () => {
-            if (this.sandbox) {
-                this.sandbox.resize();
-            }
-        })
+    'shared.renderer'() {
+      this.refreshAll();
     },
-
-    computed: {
-        editLink() {
-            const params = ['c=' + URL_PARAMS.c];
-            if (URL_PARAMS.theme) {
-                params.push(['theme=' + URL_PARAMS.theme]);
-            }
-            if (URL_PARAMS.gl) {
-                params.push(['gl=' + URL_PARAMS.gl]);
-            }
-            return './editor.html?' + params.join('&');
-        }
+    'shared.darkMode'() {
+      this.refreshAll();
     },
-
-    watch: {
-        'shared.runCode'(val) {
-            if (this.autoRun || !this.sandbox) {
-                if (!this.debouncedRun) {
-                    // First run
-                    this.run();
-                }
-                else {
-                    this.debouncedRun();
-                }
-            }
-        },
-        'shared.renderer'() {
-            this.refreshAll();
-        },
-        'shared.darkMode'() {
-            this.refreshAll();
-        },
-        'shared.enableDecal'() {
-            this.refreshAll();
-        },
-        'shared.useDirtyRect'() {
-            this.refreshAll();
-        }
+    'shared.enableDecal'() {
+      this.refreshAll();
     },
-
-    methods: {
-        run,
-        // debouncedRun will be created at first run
-        // debouncedRun: null,
-        refreshAll() {
-            this.dispose();
-            this.run();
-        },
-        dispose() {
-            if (this.sandbox) {
-                this.sandbox.dispose();
-            }
-        },
-        downloadExample() {
-            download();
-        },
-        screenshot() {
-            if (this.sandbox) {
-                const url = this.sandbox.getDataURL();
-                const $a = document.createElement('a');
-                $a.download = URL_PARAMS.c + '.' + (store.renderer === 'svg' ? 'svg' : 'png');
-                $a.target = '_blank';
-                $a.href = url;
-                const evt = new MouseEvent('click', {
-                    bubbles: true,
-                    cancelable: false
-                });
-                $a.dispatchEvent(evt);
-            }
-        },
-        getOption() {
-            return this.sandbox && this.sandbox.getOption();
-        }
-        // hasEditorError() {
-        //     const annotations = this.editor.getSession().getAnnotations();
-        //     for (let aid = 0, alen = annotations.length; aid < alen; ++aid) {
-        //         if (annotations[aid].type === 'error') {
-        //             return true;
-        //         }
-        //     }
-        //     return false;
-        // }
+    'shared.useDirtyRect'() {
+      this.refreshAll();
     }
-}
+  },
+
+  methods: {
+    run,
+    // debouncedRun will be created at first run
+    // debouncedRun: null,
+    refreshAll() {
+      this.dispose();
+      this.run();
+    },
+    dispose() {
+      if (this.sandbox) {
+        this.sandbox.dispose();
+      }
+    },
+    downloadExample() {
+      download();
+    },
+    screenshot() {
+      if (this.sandbox) {
+        const url = this.sandbox.getDataURL();
+        const $a = document.createElement('a');
+        $a.download =
+          URL_PARAMS.c + '.' + (store.renderer === 'svg' ? 'svg' : 'png');
+        $a.target = '_blank';
+        $a.href = url;
+        const evt = new MouseEvent('click', {
+          bubbles: true,
+          cancelable: false
+        });
+        $a.dispatchEvent(evt);
+      }
+    },
+    getOption() {
+      return this.sandbox && this.sandbox.getOption();
+    }
+    // hasEditorError() {
+    //     const annotations = this.editor.getSession().getAnnotations();
+    //     for (let aid = 0, alen = annotations.length; aid < alen; ++aid) {
+    //         if (annotations[aid].type === 'error') {
+    //             return true;
+    //         }
+    //     }
+    //     return false;
+    // }
+  }
+};
 </script>
 
 <style lang="scss">
-
 #chart-panel {
-    position: absolute;
-    // top: $control-panel-height;
-    top: 42px;
-    right: 15px;
-    bottom: 15px;
-    left: 15px;
-    box-sizing: border-box;
-    box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 20px;
-    border-radius: 5px;
-    background: #fff;
-    overflow: hidden;
-
-    padding: 10px;
-
-    .ec-debug-dirty-rect-container {
-        left: 10px!important;
-        top: 10px!important;
-        right: 10px!important;
-        bottom: 10px!important;
-
-        .ec-debug-dirty-rect {
-            background-color: rgba(255, 0, 0, 0.2)!important;
-            border: 1px solid red!important;
-        }
+  position: absolute;
+  // top: $control-panel-height;
+  top: 42px;
+  right: 15px;
+  bottom: 15px;
+  left: 15px;
+  box-sizing: border-box;
+  box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 20px;
+  border-radius: 5px;
+  background: #fff;
+  overflow: hidden;
+
+  padding: 10px;
+
+  .ec-debug-dirty-rect-container {
+    left: 10px !important;
+    top: 10px !important;
+    right: 10px !important;
+    bottom: 10px !important;
+
+    .ec-debug-dirty-rect {
+      background-color: rgba(255, 0, 0, 0.2) !important;
+      border: 1px solid red !important;
     }
+  }
 
-    .chart-container {
-        position: relative;
-        height: 100%;
-    }
+  .chart-container {
+    position: relative;
+    height: 100%;
+  }
 }
 
 .render-config-container {
-    .el-radio-group {
-        label {
-            margin-bottom: 0;
-        }
+  .el-radio-group {
+    label {
+      margin-bottom: 0;
     }
+  }
 }
 
 #tool-panel {
-    position: absolute;
-    top: 0;
-    right: 0;
-    left: 0;
-    padding-top: 5px;
-    padding-left: 15px;
-
-    // .el-switch__label * {
-    //     font-size: 12px;
-    // }
-
-    .render-config-trigger {
-        margin-left: 10px;
-        cursor: pointer;
-        font-weight: 500;
-        // font-size: 12px;
-    }
-
-    label {
-        margin-bottom: 0;
-    }
-
-    .left-panel {
-        float: left;
-    }
-
-    .left-panel>* {
-        vertical-align: middle;
-        display: inline-block;
-    }
-
-    .tool-label {
-        font-weight: bold;
-        text-transform: uppercase;
-        margin-left: 20px;
-    }
-
-    .screenshot, .download, .edit {
-        float: right;
-        margin-right: 15px;
-        cursor: pointer;
-    }
-    .screenshot {
-        font-size: 22px;
-        margin-right: 10px;
-    }
+  position: absolute;
+  top: 0;
+  right: 0;
+  left: 0;
+  padding-top: 5px;
+  padding-left: 15px;
+
+  // .el-switch__label * {
+  //     font-size: 12px;
+  // }
+
+  .render-config-trigger {
+    margin-left: 10px;
+    cursor: pointer;
+    font-weight: 500;
+    // font-size: 12px;
+  }
+
+  label {
+    margin-bottom: 0;
+  }
+
+  .left-panel {
+    float: left;
+  }
+
+  .left-panel > * {
+    vertical-align: middle;
+    display: inline-block;
+  }
+
+  .tool-label {
+    font-weight: bold;
+    text-transform: uppercase;
+    margin-left: 20px;
+  }
+
+  .screenshot,
+  .download,
+  .edit {
+    float: right;
+    margin-right: 15px;
+    cursor: pointer;
+  }
+  .screenshot {
+    font-size: 22px;
+    margin-right: 10px;
+  }
 }
 
 .full {
-    #chart-panel {
-        top: 40px;
-        right: 5px;
-        bottom: 5px;
-        left: 5px;
-        box-shadow: rgba(10, 9, 9, 0.1) 0px 0px 5px;
-    }
-    #tool-panel {
-        padding-left: 5px;
-        .download, .edit {
-            margin-right: 5px;
-        }
+  #chart-panel {
+    top: 40px;
+    right: 5px;
+    bottom: 5px;
+    left: 5px;
+    box-shadow: rgba(10, 9, 9, 0.1) 0px 0px 5px;
+  }
+  #tool-panel {
+    padding-left: 5px;
+    .download,
+    .edit {
+      margin-right: 5px;
     }
+  }
 }
 
 .dg.main * {
-    box-sizing: content-box;
+  box-sizing: content-box;
 }
 .dg.main input {
-    line-height: normal;
+  line-height: normal;
 }
 
 .dg.main.a {
-    overflow-x: visible;
+  overflow-x: visible;
 }
 
 .dg.main .c {
-    select {
-        color: #000;
-    }
+  select {
+    color: #000;
+  }
 }
-
-</style>
\ No newline at end of file
+</style>
diff --git a/src/editor/View.vue b/src/editor/View.vue
index f6abe53..a8b1ca1 100644
--- a/src/editor/View.vue
+++ b/src/editor/View.vue
@@ -1,25 +1,22 @@
 <template>
-<preview></Preview>
+  <preview></preview>
 </template>
 
 <script>
-
 import Preview from './Preview.vue';
-import {store, loadExampleCode, parseSourceCode} from '../common/store';
+import { store, loadExampleCode, parseSourceCode } from '../common/store';
 
 export default {
-    components: {
-        Preview
-    },
+  components: {
+    Preview
+  },
 
-    mounted() {
-        loadExampleCode().then(code => {
-            store.runCode = parseSourceCode(code);
-        });
-    }
-}
+  mounted() {
+    loadExampleCode().then((code) => {
+      store.runCode = parseSourceCode(code);
+    });
+  }
+};
 </script>
 
-<style lang="scss">
-
-</style>
\ No newline at end of file
+<style lang="scss"></style>
diff --git a/src/editor/downloadExample.js b/src/editor/downloadExample.js
index b9f1a48..d4235df 100644
--- a/src/editor/downloadExample.js
+++ b/src/editor/downloadExample.js
@@ -1,12 +1,12 @@
-import {store} from '../common/store';
-import {URL_PARAMS, SCRIPT_URLS} from '../common/config';
-import {downloadBlob} from '../common/helper';
+import { store } from '../common/store';
+import { URL_PARAMS, SCRIPT_URLS } from '../common/config';
+import { downloadBlob } from '../common/helper';
 
 const hasRootPath = store.sourceCode.indexOf('ROOT_PATH') >= 0;
 const rootPathCode = hasRootPath ? `var ROOT_PATH = '${store.cdnRoot}'` : '';
 
 export function download() {
-    const code = `<!--
+  const code = `<!--
     THIS EXAMPLE WAS DOWNLOADED FROM ${window.location.href}
 -->
 <!DOCTYPE html>
@@ -55,10 +55,10 @@ if (option && typeof option === 'object') {
     </body>
 </html>
     `;
-    const file = new Blob([code], {
-        type: 'text/html;charset=UTF-8',
-        encoding: 'UTF-8'
-    });
-    // download the blob
-    downloadBlob(file, URL_PARAMS.c + '.html');
-}
\ No newline at end of file
+  const file = new Blob([code], {
+    type: 'text/html;charset=UTF-8',
+    encoding: 'UTF-8'
+  });
+  // download the blob
+  downloadBlob(file, URL_PARAMS.c + '.html');
+}
diff --git a/src/editor/object-visualizer.css b/src/editor/object-visualizer.css
index 372a9fd..f135039 100644
--- a/src/editor/object-visualizer.css
+++ b/src/editor/object-visualizer.css
@@ -1,72 +1,72 @@
 .object-visualizer {
-    border-radius: 4px;
-    overflow-x: auto;
-    margin: 0;
-    padding: 10px;
-    /* font-family: Menlo; */
-    font-size: 0.6em;
-    line-height: 1.7;
-  }
+  border-radius: 4px;
+  overflow-x: auto;
+  margin: 0;
+  padding: 10px;
+  /* font-family: Menlo; */
+  font-size: 0.6em;
+  line-height: 1.7;
+}
 
-  .array > .value,
-  .object > .value {
-    display: flex;
-    flex-direction: column;
-    margin-left: 2em;
-  }
+.array > .value,
+.object > .value {
+  display: flex;
+  flex-direction: column;
+  margin-left: 2em;
+}
 
-  .key {
-    /* color: #4078f2; */
-    color: #111;
-    user-select: none;
-  }
+.key {
+  /* color: #4078f2; */
+  color: #111;
+  user-select: none;
+}
 
-  .string > .value {
-    color: #50a14f;
-  }
+.string > .value {
+  color: #50a14f;
+}
 
-  .boolean > .value,
-  .number > .value {
-    color: #d19a66;
-  }
+.boolean > .value,
+.number > .value {
+  color: #d19a66;
+}
 
-  .null > .value,
-  .undefined > .value {
-    color: hsl(0, 0%, 40%);
-  }
+.null > .value,
+.undefined > .value {
+  color: hsl(0, 0%, 40%);
+}
 
-  .separator {
-    cursor: pointer;
-    font-size: 0.8em;
-    user-select: none;
-    color: hsl(0, 0%, 30%);
-  }
+.separator {
+  cursor: pointer;
+  font-size: 0.8em;
+  user-select: none;
+  color: hsl(0, 0%, 30%);
+}
 
-  .indicator {
-    cursor: pointer;
-    font-size: 0.8em;
-    padding-right: 0.5em;
-    user-select: none;
-    vertical-align: text-bottom;
-    color: hsl(0, 0%, 30%);
-  }
+.indicator {
+  cursor: pointer;
+  font-size: 0.8em;
+  padding-right: 0.5em;
+  user-select: none;
+  vertical-align: text-bottom;
+  color: hsl(0, 0%, 30%);
+}
 
-  .array > .key,
-  .object > .key {
-    cursor: pointer;
-  }
+.array > .key,
+.object > .key {
+  cursor: pointer;
+}
 
-  .value > .array,
-  .value > .object {
-    position: relative;
-    left: -0.8em;
-  }
+.value > .array,
+.value > .object {
+  position: relative;
+  left: -0.8em;
+}
 
-  .count,
-  .preview,
-  .quotes {
-    cursor: pointer;
-    font-size: 0.8em;
-    user-select: none;
-    color: hsl(0, 0%, 30%);
-  }
+.count,
+.preview,
+.quotes {
+  cursor: pointer;
+  font-size: 0.8em;
+  user-select: none;
+  color: hsl(0, 0%, 30%);
+}
diff --git a/src/editor/sandbox.js b/src/editor/sandbox.js
index 4786d75..de496c5 100644
--- a/src/editor/sandbox.js
+++ b/src/editor/sandbox.js
@@ -1,190 +1,210 @@
 import showDebugDirtyRect from '../dep/showDebugDirtyRect';
 
 export function createSandbox(optionUpdated) {
-    let appEnv = {};
-    let gui;
-
-    let _intervalIdList = [];
-    let _timeoutIdList = [];
-
-    const _oldSetTimeout = window.setTimeout;
-    const _oldSetInterval = window.setInterval;
-
-    function setTimeout(func, delay) {
-        var id = _oldSetTimeout(func, delay);
-        _timeoutIdList.push(id);
-        return id;
+  let appEnv = {};
+  let gui;
+
+  let _intervalIdList = [];
+  let _timeoutIdList = [];
+
+  const _oldSetTimeout = window.setTimeout;
+  const _oldSetInterval = window.setInterval;
+
+  function setTimeout(func, delay) {
+    var id = _oldSetTimeout(func, delay);
+    _timeoutIdList.push(id);
+    return id;
+  }
+  function setInterval(func, gap) {
+    var id = _oldSetInterval(func, gap);
+    _intervalIdList.push(id);
+    return id;
+  }
+  function _clearTimeTickers() {
+    for (var i = 0; i < _intervalIdList.length; i++) {
+      clearInterval(_intervalIdList[i]);
+    }
+    for (var i = 0; i < _timeoutIdList.length; i++) {
+      clearTimeout(_timeoutIdList[i]);
+    }
+    _intervalIdList = [];
+    _timeoutIdList = [];
+  }
+  const _events = [];
+  function _wrapOnMethods(chart) {
+    const oldOn = chart.on;
+    const oldSetOption = chart.setOption;
+    chart.on = function (eventName) {
+      const res = oldOn.apply(chart, arguments);
+      _events.push(eventName);
+      return res;
     };
-    function setInterval(func, gap) {
-        var id = _oldSetInterval(func, gap);
-        _intervalIdList.push(id);
-        return id;
+    chart.setOption = function () {
+      const res = oldSetOption.apply(this, arguments);
+      optionUpdated && optionUpdated(chart);
+      return res;
     };
-    function _clearTimeTickers() {
-        for (var i = 0; i < _intervalIdList.length; i++) {
-            clearInterval(_intervalIdList[i]);
-        }
-        for (var i = 0; i < _timeoutIdList.length; i++) {
-            clearTimeout(_timeoutIdList[i]);
+  }
+
+  function _clearChartEvents(chart) {
+    _events.forEach(function (eventName) {
+      if (chart) {
+        chart.off(eventName);
+      }
+    });
+
+    _events.length = 0;
+  }
+
+  let chartInstance;
+
+  return {
+    resize() {
+      if (chartInstance) {
+        chartInstance.resize();
+      }
+    },
+
+    dispose() {
+      if (chartInstance) {
+        chartInstance.dispose();
+        chartInstance = null;
+      }
+    },
+
+    getDataURL() {
+      return chartInstance.getDataURL({
+        pixelRatio: 2,
+        excludeComponents: ['toolbox']
+      });
+    },
+
+    getOption() {
+      return chartInstance.getOption();
+    },
+
+    run(el, store) {
+      if (!chartInstance) {
+        chartInstance = echarts.init(el, store.darkMode ? 'dark' : '', {
+          renderer: store.renderer,
+          useDirtyRect: store.useDirtyRect
+        });
+        if (store.useDirtyRect && store.renderer === 'canvas') {
+          try {
+            showDebugDirtyRect(chartInstance.getZr(), {
+              autoHideDelay: 500
+            });
+          } catch (e) {
+            console.error(e);
+          }
         }
-        _intervalIdList = [];
-        _timeoutIdList = [];
-    }
-    const _events = [];
-    function _wrapOnMethods(chart) {
-        const oldOn = chart.on;
-        const oldSetOption = chart.setOption;
-        chart.on = function (eventName) {
-            const res = oldOn.apply(chart, arguments);
-            _events.push(eventName);
-            return res;
-        };
-        chart.setOption = function () {
-            const res = oldSetOption.apply(this, arguments);
-            optionUpdated && optionUpdated(chart);
-            return res;
-        };
-    }
-
-    function _clearChartEvents(chart) {
-        _events.forEach(function (eventName) {
-            if (chart) {
-                chart.off(eventName);
-            }
+        _wrapOnMethods(chartInstance);
+      }
+
+      // if (this.hasEditorError()) {
+      //     log(this.$t('editor.errorInEditor'), 'error');
+      //     return;
+      // }
+
+      // TODO Scope the variables in component.
+      _clearTimeTickers();
+      _clearChartEvents(chartInstance);
+      // Reset
+      appEnv.config = null;
+
+      // run the code
+
+      const compiledCode = store.runCode;
+
+      const func = new Function(
+        'myChart',
+        'app',
+        'setTimeout',
+        'setInterval',
+        'ROOT_PATH',
+        'var option;\n' + compiledCode + '\nreturn option;'
+      );
+      const option = func(
+        chartInstance,
+        appEnv,
+        setTimeout,
+        setInterval,
+        store.cdnRoot
+      );
+      let updateTime = 0;
+
+      if (option && typeof option === 'object') {
+        const startTime = +new Date();
+        chartInstance.setOption(option, true);
+        const endTime = +new Date();
+        updateTime = endTime - startTime;
+      }
+
+      if (gui) {
+        $(gui.domElement).remove();
+        gui.destroy();
+        gui = null;
+      }
+
+      if (appEnv.config) {
+        gui = new dat.GUI({
+          autoPlace: false
         });
-
-        _events.length = 0;
-    }
-
-    let chartInstance;
-
-    return {
-        resize() {
-            if (chartInstance) {
-                chartInstance.resize();
-            }
-        },
-
-        dispose() {
-            if (chartInstance) {
-                chartInstance.dispose();
-                chartInstance = null;
+        $(gui.domElement).css({
+          position: 'absolute',
+          right: 5,
+          top: 0,
+          zIndex: 1000
+        });
+        $('.right-container').append(gui.domElement);
+
+        var configParameters = appEnv.configParameters || {};
+        for (var name in appEnv.config) {
+          var value = appEnv.config[name];
+          if (name !== 'onChange' && name !== 'onFinishChange') {
+            var isColor = false;
+            // var value = obj;
+            var controller = null;
+            if (configParameters[name]) {
+              if (configParameters[name].options) {
+                controller = gui.add(
+                  appEnv.config,
+                  name,
+                  configParameters[name].options
+                );
+              } else if (configParameters[name].min != null) {
+                controller = gui.add(
+                  appEnv.config,
+                  name,
+                  configParameters[name].min,
+                  configParameters[name].max
+                );
+              }
             }
-        },
-
-        getDataURL() {
-            return chartInstance.getDataURL({
-                pixelRatio: 2,
-                excludeComponents: ['toolbox']
-            });
-        },
-
-        getOption() {
-            return chartInstance.getOption();
-        },
-
-        run(el, store) {
-
-            if (!chartInstance) {
-                chartInstance = echarts.init(el, store.darkMode ? 'dark' : '', {
-                    renderer: store.renderer,
-                    useDirtyRect: store.useDirtyRect
-                });
-                if (store.useDirtyRect && store.renderer === 'canvas') {
-                    try {
-                        showDebugDirtyRect(chartInstance.getZr(), {
-                            autoHideDelay: 500
-                        });
-                    }
-                    catch (e) {
-                        console.error(e);
-                    }
+            if (typeof obj === 'string') {
+              try {
+                var colorArr = echarts.color.parse(value);
+                isColor = !!colorArr;
+                if (isColor) {
+                  value = echarts.color.stringify(colorArr, 'rgba');
                 }
-                _wrapOnMethods(chartInstance);
-            }
-
-            // if (this.hasEditorError()) {
-            //     log(this.$t('editor.errorInEditor'), 'error');
-            //     return;
-            // }
-
-            // TODO Scope the variables in component.
-            _clearTimeTickers();
-            _clearChartEvents(chartInstance);
-            // Reset
-            appEnv.config = null;
-
-            // run the code
-
-            const compiledCode = store.runCode;
-
-            const func = new Function(
-                'myChart', 'app', 'setTimeout', 'setInterval', 'ROOT_PATH',
-                'var option;\n' + compiledCode + '\nreturn option;'
-            );
-            const option = func(chartInstance, appEnv, setTimeout, setInterval, store.cdnRoot);
-            let updateTime = 0;
-
-            if (option && typeof option === 'object') {
-                const startTime = +new Date();
-                chartInstance.setOption(option, true);
-                const endTime = +new Date();
-                updateTime = endTime - startTime;
+              } catch (e) {}
             }
-
-            if (gui) {
-                $(gui.domElement).remove();
-                gui.destroy();
-                gui = null;
+            if (!controller) {
+              controller = gui[isColor ? 'addColor' : 'add'](
+                appEnv.config,
+                name
+              );
             }
-
-            if (appEnv.config) {
-                gui = new dat.GUI({
-                    autoPlace: false
-                });
-                $(gui.domElement).css({
-                    position: 'absolute',
-                    right: 5,
-                    top: 0,
-                    zIndex: 1000
-                });
-                $('.right-container').append(gui.domElement);
-
-                var configParameters = appEnv.configParameters || {};
-                for (var name in appEnv.config) {
-                    var value = appEnv.config[name];
-                    if (name !== 'onChange' && name !== 'onFinishChange') {
-                        var isColor = false;
-                        // var value = obj;
-                        var controller = null;
-                        if (configParameters[name]) {
-                            if (configParameters[name].options) {
-                                controller = gui.add(appEnv.config, name, configParameters[name].options);
-                            }
-                            else if (configParameters[name].min != null) {
-                                controller = gui.add(appEnv.config, name, configParameters[name].min, configParameters[name].max);
-                            }
-                        }
-                        if (typeof obj === 'string') {
-                            try {
-                                var colorArr = echarts.color.parse(value);
-                                isColor = !!colorArr;
-                                if (isColor) {
-                                    value = echarts.color.stringify(colorArr, 'rgba');
-                                }
-                            }
-                            catch (e) {}
-                        }
-                        if (!controller) {
-                            controller = gui[isColor ? 'addColor' : 'add'](appEnv.config, name);
-                        }
-                        appEnv.config.onChange && controller.onChange(appEnv.config.onChange);
-                        appEnv.config.onFinishChange && controller.onFinishChange(appEnv.config.onFinishChange);
-                    }
-                }
-            }
-
-            return updateTime;
+            appEnv.config.onChange &&
+              controller.onChange(appEnv.config.onChange);
+            appEnv.config.onFinishChange &&
+              controller.onFinishChange(appEnv.config.onFinishChange);
+          }
         }
-    };
-};
\ No newline at end of file
+      }
+
+      return updateTime;
+    }
+  };
+}
diff --git a/src/editor/transformTs.js b/src/editor/transformTs.js
index b47973e..cb2cb1c 100644
--- a/src/editor/transformTs.js
+++ b/src/editor/transformTs.js
@@ -1,7 +1,7 @@
-import {transform} from 'sucrase';
+import { transform } from 'sucrase';
 
 export default function (code) {
-    return transform(code, {
-        transforms: ['typescript']
-    }).code.trim();
-}
\ No newline at end of file
+  return transform(code, {
+    transforms: ['typescript']
+  }).code.trim();
+}
diff --git a/src/explore/ExampleCard.vue b/src/explore/ExampleCard.vue
index bd465b8..17d6959 100644
--- a/src/explore/ExampleCard.vue
+++ b/src/explore/ExampleCard.vue
@@ -1,140 +1,144 @@
 <template>
-<div class="example-list-item">
+  <div class="example-list-item">
     <a target="_blank" class="example-link" :href="exampleLink">
-        <img class="chart-area" src="../asset/placeholder.jpg" :data-src="screenshotURL" />
-        <h4 class="example-title">{{title}}</h4>
-        <h5 class="example-subtitle" v-if="showSubtitle">{{subtitle}}</h5>
+      <img
+        class="chart-area"
+        src="../asset/placeholder.jpg"
+        :data-src="screenshotURL"
+      />
+      <h4 class="example-title">{{ title }}</h4>
+      <h5 class="example-subtitle" v-if="showSubtitle">{{ subtitle }}</h5>
     </a>
-</div>
+  </div>
 </template>
 
 <script>
-
-import {store} from '../common/store';
-import {SUPPORT_WEBP, URL_PARAMS} from '../common/config';
+import { store } from '../common/store';
+import { SUPPORT_WEBP, URL_PARAMS } from '../common/config';
 
 export default {
-    props: ['example'],
-
-    computed: {
-
-        title() {
-            return (store.locale === 'zh' ? this.example.titleCN : this.example.title)
-                || this.example.title || '';
-        },
-
-        showSubtitle() {
-            return store.locale === 'zh';
-        },
-
-        subtitle() {
-            return this.example.title || '';
-        },
-
-        exampleTheme() {
-            const example = this.example;
-            return example.theme || (store.darkMode ? 'dark' : '');
-        },
-
-        exampleLink() {
-            const example = this.example;
-            const hash = ['c=' + example.id];
-            const exampleTheme = this.exampleTheme;
-            if (example.isGL) {
-                hash.push('gl=1');
-            }
-            if (exampleTheme) {
-                hash.push('theme=' + exampleTheme);
-            }
-            if ('local' in URL_PARAMS) {
-                hash.push('local');
-            }
-            if ('useDirtyRect' in URL_PARAMS) {
-                hash.push('useDirtyRect');
-            }
-            return './editor.html?' + hash.join('&');
-        },
-
-        screenshotURL() {
-            const example = this.example;
-            const themePostfix = this.exampleTheme ? ('-' + this.exampleTheme) : '';
-            const ext = SUPPORT_WEBP ? 'webp' : 'png';
-            const folder = example.isGL ? 'data-gl' : 'data';
-            return `${store.cdnRoot}/${folder}/thumb${themePostfix}/${example.id}.${ext}?_v_=${store.version}`;
-        }
+  props: ['example'],
+
+  computed: {
+    title() {
+      return (
+        (store.locale === 'zh' ? this.example.titleCN : this.example.title) ||
+        this.example.title ||
+        ''
+      );
+    },
+
+    showSubtitle() {
+      return store.locale === 'zh';
+    },
+
+    subtitle() {
+      return this.example.title || '';
+    },
+
+    exampleTheme() {
+      const example = this.example;
+      return example.theme || (store.darkMode ? 'dark' : '');
+    },
+
+    exampleLink() {
+      const example = this.example;
+      const hash = ['c=' + example.id];
+      const exampleTheme = this.exampleTheme;
+      if (example.isGL) {
+        hash.push('gl=1');
+      }
+      if (exampleTheme) {
+        hash.push('theme=' + exampleTheme);
+      }
+      if ('local' in URL_PARAMS) {
+        hash.push('local');
+      }
+      if ('useDirtyRect' in URL_PARAMS) {
+        hash.push('useDirtyRect');
+      }
+      return './editor.html?' + hash.join('&');
+    },
+
+    screenshotURL() {
+      const example = this.example;
+      const themePostfix = this.exampleTheme ? '-' + this.exampleTheme : '';
+      const ext = SUPPORT_WEBP ? 'webp' : 'png';
+      const folder = example.isGL ? 'data-gl' : 'data';
+      return `${store.cdnRoot}/${folder}/thumb${themePostfix}/${example.id}.${ext}?_v_=${store.version}`;
     }
-}
+  }
+};
 </script>
 
 <style lang="scss">
-
-@import "../style/color.scss";
+@import '../style/color.scss';
 
 .example-list-item {
-    width: 100%;
-    max-width: 350px;
-    margin-bottom: 30px;
-
-    border-radius: 2px;
-
-    .example-link {
-        position: relative;
-        display: block;
-
-        .chart-area {
-            width: 100%;
-            height: 100%;
-            border-radius: 5px;
-            box-shadow: 0 0 20px rgba(0, 0, 0, 0.05);
-            margin-top: 10px;
-        }
-
-        .example-title {
-            color: $clr-primary;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            white-space: nowrap;
-
-            padding: 10px 10px 2px 10px;
-            margin: 0;
-            font-weight: normal;
-            font-size: 14px;
-            text-align: center;
-        }
-
-        .example-subtitle {
-            font-size: 12px;
-            text-align: center;
-            color: #aaa;
-            font-weight: normal;
-            // font-weight: 200;
-            overflow: hidden;
-            white-space: nowrap;
-            text-overflow: ellipsis;
-            margin: 3px 0 0 0;
-        }
+  width: 100%;
+  max-width: 350px;
+  margin-bottom: 30px;
+
+  border-radius: 2px;
+
+  .example-link {
+    position: relative;
+    display: block;
+
+    .chart-area {
+      width: 100%;
+      height: 100%;
+      border-radius: 5px;
+      box-shadow: 0 0 20px rgba(0, 0, 0, 0.05);
+      margin-top: 10px;
+    }
+
+    .example-title {
+      color: $clr-primary;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+
+      padding: 10px 10px 2px 10px;
+      margin: 0;
+      font-weight: normal;
+      font-size: 14px;
+      text-align: center;
+    }
+
+    .example-subtitle {
+      font-size: 12px;
+      text-align: center;
+      color: #aaa;
+      font-weight: normal;
+      // font-weight: 200;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      margin: 3px 0 0 0;
     }
+  }
 
-    .example-info {
-        padding: 5px 0;
+  .example-info {
+    padding: 5px 0;
 
-        font-weight: bold;
+    font-weight: bold;
 
-        .chart-icon {
-            float: right;
+    .chart-icon {
+      float: right;
 
-            .chart-delete {
-                display: none;
+      .chart-delete {
+        display: none;
 
-                transition: 1s;
-            }
-        }
+        transition: 1s;
+      }
     }
+  }
 
-    &:hover .example-info .chart-icon .chart-delete {
-        display: block;
+  &:hover .example-info .chart-icon .chart-delete {
+    display: block;
 
-        text-decoration: none;
-    }
+    text-decoration: none;
+  }
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/explore/Explore.vue b/src/explore/Explore.vue
index 873674e..cd81d4c 100644
--- a/src/explore/Explore.vue
+++ b/src/explore/Explore.vue
@@ -1,209 +1,251 @@
 <template>
-<div id="example-explore">
+  <div id="example-explore">
     <div id="left-container">
-        <div id="left-chart-nav">
-            <scrollactive
-                active-class="active"
-                :offset="80"
-                :duration="500"
-                :scroll-container-selector="'#example-explore'"
-                bezier-easing-value=".5,0,.35,1"
-                @itemchanged="onActiveNavChanged"
-            >
-                <ul>
-                    <li v-for="category in EXAMPLE_CATEGORIES" :key="category">
-                        <a class="left-chart-nav-link scrollactive-item" :id="'left-chart-nav-' + category"
-                            :href="'#chart-type-' + category"
-                        >
-                            <span class="chart-icon" v-html="icons[category]"></span>
-                            <span class="chart-name">{{$t('chartTypes.' + category)}}</span>
-                        </a>
-                    </li>
-                </ul>
-            </scrollactive>
-        </div>
+      <div id="left-chart-nav">
+        <scrollactive
+          active-class="active"
+          :offset="80"
+          :duration="500"
+          :scroll-container-selector="'#example-explore'"
+          bezier-easing-value=".5,0,.35,1"
+          @itemchanged="onActiveNavChanged"
+        >
+          <ul>
+            <li v-for="category in EXAMPLE_CATEGORIES" :key="category">
+              <a
+                class="left-chart-nav-link scrollactive-item"
+                :id="'left-chart-nav-' + category"
+                :href="'#chart-type-' + category"
+              >
+                <span class="chart-icon" v-html="icons[category]"></span>
+                <span class="chart-name">{{
+                  $t('chartTypes.' + category)
+                }}</span>
+              </a>
+            </li>
+          </ul>
+        </scrollactive>
+      </div>
     </div>
     <div id="explore-container">
-        <div class="example-list-panel">
-            <div v-for="categoryObj in exampleList" :key="categoryObj.category">
-                <h3 class="chart-type-head" :id="'chart-type-' + categoryObj.category">
-                    {{$t('chartTypes.' + categoryObj.category)}}
-                    <span>{{categoryObj.category}}</span>
-                </h3>
-
-                <div class="row" :id="'chart-row-' + categoryObj.category">
-                    <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6"
-                        v-for="exampleItem in categoryObj.examples"
-                        :key="exampleItem.id"
-                    >
-                        <ExampleCard :example="exampleItem"></ExampleCard>
-                    </div>
-                </div>
+      <div class="example-list-panel">
+        <div v-for="categoryObj in exampleList" :key="categoryObj.category">
+          <h3
+            class="chart-type-head"
+            :id="'chart-type-' + categoryObj.category"
+          >
+            {{ $t('chartTypes.' + categoryObj.category) }}
+            <span>{{ categoryObj.category }}</span>
+          </h3>
+
+          <div class="row" :id="'chart-row-' + categoryObj.category">
+            <div
+              class="col-xl-2 col-lg-3 col-md-4 col-sm-6"
+              v-for="exampleItem in categoryObj.examples"
+              :key="exampleItem.id"
+            >
+              <ExampleCard :example="exampleItem"></ExampleCard>
             </div>
+          </div>
         </div>
+      </div>
     </div>
     <div id="toolbar">
-        <el-switch
-            v-model="shared.darkMode"
-            active-color="#181432"
-            :active-text="$t('editor.darkMode')"
-            :inactive-text="''">
-        </el-switch>
+      <el-switch
+        v-model="shared.darkMode"
+        active-color="#181432"
+        :active-text="$t('editor.darkMode')"
+        :inactive-text="''"
+      >
+      </el-switch>
     </div>
-</div>
+  </div>
 </template>
 
 <script>
-
 import CHART_LIST from '../data/chart-list-data';
 import CHART_LIST_GL from '../data/chart-list-data-gl';
-import {EXAMPLE_CATEGORIES, BLACK_MAP, URL_PARAMS} from '../common/config';
-import {store} from '../common/store';
+import { EXAMPLE_CATEGORIES, BLACK_MAP, URL_PARAMS } from '../common/config';
+import { store } from '../common/store';
 import ExampleCard from './ExampleCard.vue';
 import LazyLoad from 'vanilla-lazyload/dist/lazyload.esm';
 // import scrollIntoView from 'scroll-into-view';
 
 const icons = {};
 
-['line', 'bar', 'scatter', 'pie', 'radar', 'funnel', 'gauge', 'map',
-    'graph', 'treemap', 'parallel', 'sankey', 'candlestick', 'boxplot', 'heatmap',
-    'pictorialBar', 'themeRiver', 'calendar', 'custom', 'sunburst', 'tree', 'dataset', 'geo', 'lines',
-    'dataZoom', 'rich', 'drag'
+[
+  'line',
+  'bar',
+  'scatter',
+  'pie',
+  'radar',
+  'funnel',
+  'gauge',
+  'map',
+  'graph',
+  'treemap',
+  'parallel',
+  'sankey',
+  'candlestick',
+  'boxplot',
+  'heatmap',
+  'pictorialBar',
+  'themeRiver',
+  'calendar',
+  'custom',
+  'sunburst',
+  'tree',
+  'dataset',
+  'geo',
+  'lines',
+  'dataZoom',
+  'rich',
+  'drag'
 ].forEach(function (category) {
-    icons[category] = require('../asset/icon/' + category + '.svg');
+  icons[category] = require('../asset/icon/' + category + '.svg');
 });
 
 const glIcon = require('../asset/icon/gl.svg');
-['globe', 'bar3D', 'scatter3D', 'surface', 'map3D', 'lines3D', 'line3D',
-    'scatterGL', 'linesGL', 'flowGL', 'graphGL', 'geo3D'].forEach(function (category) {
-    icons[category] = glIcon;
+[
+  'globe',
+  'bar3D',
+  'scatter3D',
+  'surface',
+  'map3D',
+  'lines3D',
+  'line3D',
+  'scatterGL',
+  'linesGL',
+  'flowGL',
+  'graphGL',
+  'geo3D'
+].forEach(function (category) {
+  icons[category] = glIcon;
 });
 
 const LAZY_LOADED_CLASS = 'ec-shot-loaded';
 
 export default {
+  components: {
+    ExampleCard
+  },
+
+  data() {
+    const exampleListByCategory = {};
+
+    function addExamples(list, isGL) {
+      let categoryOrder = 0;
+      // Add by category order in each example.
+      do {
+        let added = false;
+        for (let i = 0; i < list.length; i++) {
+          const example = list[i];
+          if (BLACK_MAP.hasOwnProperty(example.id)) {
+            continue;
+          }
+          if (typeof example.category === 'string') {
+            example.category = [example.category];
+          }
+
+          const categoryStr = (example.category || [])[categoryOrder];
+          if (categoryStr) {
+            added = true;
+            let categoryObj = exampleListByCategory[categoryStr];
+            if (!categoryObj) {
+              categoryObj = {
+                category: categoryStr,
+                examples: []
+              };
+              exampleListByCategory[categoryStr] = categoryObj;
+            }
+            example.isGL = isGL;
 
-    components: {
-        ExampleCard
-    },
-
-    data() {
-        const exampleListByCategory = {};
-
-        function addExamples(list, isGL) {
-            let categoryOrder = 0;
-            // Add by category order in each example.
-            do {
-                let added = false;
-                for (let i = 0; i < list.length; i++) {
-                    const example = list[i];
-                    if (BLACK_MAP.hasOwnProperty(example.id)) {
-                        continue;
-                    }
-                    if (typeof example.category === 'string') {
-                        example.category = [example.category];
-                    }
-
-                    const categoryStr = (example.category || [])[categoryOrder];
-                    if (categoryStr) {
-                        added = true;
-                        let categoryObj = exampleListByCategory[categoryStr];
-                        if (!categoryObj) {
-                            categoryObj = {
-                                category: categoryStr,
-                                examples: []
-                            }
-                            exampleListByCategory[categoryStr] = categoryObj;
-                        }
-                        example.isGL = isGL;
-
-                        categoryObj.examples.push(example);
-                    }
-                }
-
-                if (!added) {
-                    break;
-                }
-            } while (++categoryOrder && categoryOrder < 4)  // At most 4 category
+            categoryObj.examples.push(example);
+          }
         }
 
-        addExamples(CHART_LIST, false);
-        addExamples(CHART_LIST_GL, true);
-
-        return {
-            shared: store,
-
-            icons,
-
-            EXAMPLE_CATEGORIES,
-            // [{
-            //  category: '',
-            //  isGL: false
-            //  examples: []
-            // }]
-            exampleListByCategory
-        }
-    },
-
-    watch: {
-        "shared.darkMode"() {
-            const imgs = this.$el.querySelectorAll('img.chart-area');
-            for (let i = 0; i < imgs.length; i++) {
-                // Force lazyload to update
-                imgs[i].classList.remove(LAZY_LOADED_CLASS);
-                imgs[i].setAttribute('data-was-processed', 'false');
-            }
-            this._lazyload.update();
+        if (!added) {
+          break;
         }
-    },
-
-    computed: {
-        exampleList() {
-            const list = [];
-            for (let i = 0; i < EXAMPLE_CATEGORIES.length; i++) {
-                const category = EXAMPLE_CATEGORIES[i];
-                const categoryObj = this.exampleListByCategory[category];
-                if (categoryObj && categoryObj.examples.length > 0) {
-                    list.push({
-                        category,
-                        examples: categoryObj.examples
-                    });
-                }
-            }
-            return list;
-        }
-    },
-
-    mounted() {
-        this._lazyload = new LazyLoad({
-            // Container should be the scroll viewport.
-            // container: this.$el.querySelector('#explore-container .example-list-panel'),
-            elements_selector: 'img.chart-area',
-            load_delay: 400,
-            class_loaded: LAZY_LOADED_CLASS
-        });
-    },
-
-    methods: {
-        onActiveNavChanged(event, currentItem, lastActiveItem) {
-            // currentItem && currentItem.scrollIntoView && currentItem.parentNode.scrollIntoView();
-            // scrollIntoView(currentItem, {
-            //     time: 100,
-            //     cancellable: false,
-            //     align: {
-            //         top: 0,
-            //         topOffset: 50
-            //     }
-            // });
+      } while (++categoryOrder && categoryOrder < 4); // At most 4 category
+    }
+
+    addExamples(CHART_LIST, false);
+    addExamples(CHART_LIST_GL, true);
+
+    return {
+      shared: store,
+
+      icons,
+
+      EXAMPLE_CATEGORIES,
+      // [{
+      //  category: '',
+      //  isGL: false
+      //  examples: []
+      // }]
+      exampleListByCategory
+    };
+  },
+
+  watch: {
+    'shared.darkMode'() {
+      const imgs = this.$el.querySelectorAll('img.chart-area');
+      for (let i = 0; i < imgs.length; i++) {
+        // Force lazyload to update
+        imgs[i].classList.remove(LAZY_LOADED_CLASS);
+        imgs[i].setAttribute('data-was-processed', 'false');
+      }
+      this._lazyload.update();
+    }
+  },
+
+  computed: {
+    exampleList() {
+      const list = [];
+      for (let i = 0; i < EXAMPLE_CATEGORIES.length; i++) {
+        const category = EXAMPLE_CATEGORIES[i];
+        const categoryObj = this.exampleListByCategory[category];
+        if (categoryObj && categoryObj.examples.length > 0) {
+          list.push({
+            category,
+            examples: categoryObj.examples
+          });
         }
+      }
+      return list;
     }
-}
+  },
+
+  mounted() {
+    this._lazyload = new LazyLoad({
+      // Container should be the scroll viewport.
+      // container: this.$el.querySelector('#explore-container .example-list-panel'),
+      elements_selector: 'img.chart-area',
+      load_delay: 400,
+      class_loaded: LAZY_LOADED_CLASS
+    });
+  },
+
+  methods: {
+    onActiveNavChanged(event, currentItem, lastActiveItem) {
+      // currentItem && currentItem.scrollIntoView && currentItem.parentNode.scrollIntoView();
+      // scrollIntoView(currentItem, {
+      //     time: 100,
+      //     cancellable: false,
+      //     align: {
+      //         top: 0,
+      //         topOffset: 50
+      //     }
+      // });
+    }
+  }
+};
 </script>
 
 <style lang="scss">
-
-@import "../style/color.scss";
-@import "../style/config.xl.scss";
+@import '../style/color.scss';
+@import '../style/config.xl.scss';
 
 $chart-nav-width: 200px;
 $chart-icon-width: 25px;
@@ -212,10 +254,10 @@ $chart-icon-border: 1px;
 $toolbar-height: 30px;
 
 $nav-height: 50px;
-$nav-active-bg: #5470C6;
+$nav-active-bg: #5470c6;
 $nav-hover-border: $nav-active-bg;
 
-$nav-text-color: #6E7079;
+$nav-text-color: #6e7079;
 $nav-hover-text-color: #464646;
 
 $pd-basic: 10px;
@@ -223,178 +265,176 @@ $pd-sm: 6px;
 $pd-lg: 20px;
 
 #example-explore {
-    background: $clr-bg;
-
-    // Use this as scrollable viewport insteadof window because echarts-www has a viewport.
-    position: absolute;
-    top: 0;
-    right: 0;
-    bottom: 0;
-    left: 0;
-    overflow-y: auto;
-
-    ::-webkit-scrollbar {
-        height: 4px;
-        width: 4px;
-        -webkit-transition: all 0.3s ease-in-out;
-        transition: all 0.3s ease-in-out;
-        border-radius: 2px;
-        background: #fff;
-    }
-
-    ::-webkit-scrollbar-button {
-        display: none
-    }
-
-    ::-webkit-scrollbar-thumb {
-        width: 4px;
-        min-height: 15px;
-        background: rgba(50, 50, 50, 0.2) !important;
-        -webkit-transition: all 0.3s ease-in-out;
-        transition: all 0.3s ease-in-out;
-        border-radius: 2px
-    }
-
-    ::-webkit-scrollbar-thumb:hover {
-        background: rgba(0, 0, 0, 0.5) !important
-    }
+  background: $clr-bg;
+
+  // Use this as scrollable viewport insteadof window because echarts-www has a viewport.
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  overflow-y: auto;
+
+  ::-webkit-scrollbar {
+    height: 4px;
+    width: 4px;
+    -webkit-transition: all 0.3s ease-in-out;
+    transition: all 0.3s ease-in-out;
+    border-radius: 2px;
+    background: #fff;
+  }
+
+  ::-webkit-scrollbar-button {
+    display: none;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    width: 4px;
+    min-height: 15px;
+    background: rgba(50, 50, 50, 0.2) !important;
+    -webkit-transition: all 0.3s ease-in-out;
+    transition: all 0.3s ease-in-out;
+    border-radius: 2px;
+  }
+
+  ::-webkit-scrollbar-thumb:hover {
+    background: rgba(0, 0, 0, 0.5) !important;
+  }
 }
 
 #explore-container {
-    margin-left: $chart-nav-width + 20px;
-    padding: 10px 10px;
-    // background-color: $clr-bg;
+  margin-left: $chart-nav-width + 20px;
+  padding: 10px 10px;
+  // background-color: $clr-bg;
 }
 
 .example-list-panel {
-    margin: 30px 15px 30px 15px;
-
-    h3 {
-        margin-bottom: 20px;
-        padding-bottom: 10px;
-        border-bottom: 1px solid #E1E5F2;
-        font-weight: normal;
-        color: #464646;
-        font-size: 20px;
-    }
-    .chart-type-head span {
-        font-size: 16px;
-        padding-left: 5px;
-        color: #949CB1;
-        font-weight: 200;
-    }
+  margin: 30px 15px 30px 15px;
+
+  h3 {
+    margin-bottom: 20px;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #e1e5f2;
+    font-weight: normal;
+    color: #464646;
+    font-size: 20px;
+  }
+  .chart-type-head span {
+    font-size: 16px;
+    padding-left: 5px;
+    color: #949cb1;
+    font-weight: 200;
+  }
 }
 
 #left-container {
-    position: sticky;
-    left: 0;
-    top: 0;
-    float: left;
-    height: calc(100%);
-    width: $chart-nav-width;
-    box-shadow: 0 0 10px rgba(0,0,0,0.1);
-    overflow-y: auto;
-
+  position: sticky;
+  left: 0;
+  top: 0;
+  float: left;
+  height: calc(100%);
+  width: $chart-nav-width;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+  overflow-y: auto;
 }
 
 #toolbar {
-    position: fixed;
-    right: 30px;
-    top: 20px;
-    height: $toolbar-height;
-    background-color: #fff;
-    border-radius: $toolbar-height / 2;
+  position: fixed;
+  right: 30px;
+  top: 20px;
+  height: $toolbar-height;
+  background-color: #fff;
+  border-radius: $toolbar-height / 2;
+  // color: #fff;
+  padding: 4px 15px;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+
+  .el-switch__label * {
+    font-size: 12px;
     // color: #fff;
-    padding: 4px 15px;
-    box-shadow: 0 0 10px rgba(0,0,0,0.1);
-
-    .el-switch__label * {
-        font-size: 12px;
-        // color: #fff;
-        text-transform: uppercase;
-    }
-    .el-switch__label.is-active {
-        color: #181432;
-    }
+    text-transform: uppercase;
+  }
+  .el-switch__label.is-active {
+    color: #181432;
+  }
 }
 
 #left-chart-nav {
-    background-color: #fff;
-    overflow-y: hidden;
-    color: #111;
-    box-shadow: 0 0 20px rgba(0,0,0,0.2);
+  background-color: #fff;
+  overflow-y: hidden;
+  color: #111;
+  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
 
-    &:hover {
-        overflow-y: auto;
+  &:hover {
+    overflow-y: auto;
 
-        a {
-            text-decoration: none;
-        }
+    a {
+      text-decoration: none;
     }
+  }
+
+  li {
+    // transition: background-color 0.5s;
+    cursor: pointer;
+
+    a {
+      height: 45px;
+      padding: 10px 0 10px 20px;
+      display: block;
+      // transition: background-color 0.5s;
+      text-decoration: none;
+      color: $nav-text-color;
+
+      .chart-name {
+        display: inline-block;
+        position: relative;
+        vertical-align: middle;
+        margin-left: 10px;
+      }
+
+      .chart-icon {
+        content: '';
+        width: 20px;
+        display: inline-block;
+        border-radius: 50%;
+        vertical-align: middle;
+
+        svg {
+          width: 100% !important;
+          height: auto !important;
+        }
+      }
 
-    li {
-        // transition: background-color 0.5s;
-        cursor: pointer;
-
-        a {
-            height: 45px;
-            padding: 10px 0 10px 20px;
-            display: block;
-            // transition: background-color 0.5s;
-            text-decoration: none;
-            color: $nav-text-color;
-
-            .chart-name {
-                display: inline-block;
-                position: relative;
-                vertical-align: middle;
-                margin-left: 10px;
-            }
-
-            .chart-icon {
-                content: '';
-                width: 20px;
-                display: inline-block;
-                border-radius: 50%;
-                vertical-align: middle;
-
-                svg {
-                    width: 100%!important;
-                    height: auto!important;
-                }
-            }
-
-            &.active {
-                background-color: $nav-active-bg;
-                color: #fff;
-
-                .chart-icon * {
-                    fill: #fff;
-                }
-            }
+      &.active {
+        background-color: $nav-active-bg;
+        color: #fff;
 
-            &.active:hover {
-                color: #fff;
-            }
+        .chart-icon * {
+          fill: #fff;
         }
+      }
 
-        &:hover {
-            border-right: 4px solid $nav-hover-border;
+      &.active:hover {
+        color: #fff;
+      }
+    }
 
-            a {
-                color: $nav-hover-text-color;
-            }
-        }
+    &:hover {
+      border-right: 4px solid $nav-hover-border;
+
+      a {
+        color: $nav-hover-text-color;
+      }
     }
+  }
 }
 
 @media (max-width: 768px) {
-    #left-container {
-        display: none;
-    }
-    #explore-container {
-        margin-left: 0;
-    }
+  #left-container {
+    display: none;
+  }
+  #explore-container {
+    margin-left: 0;
+  }
 }
-
-</style>
\ No newline at end of file
+</style>
diff --git a/src/main.js b/src/main.js
index bcb7e55..7f6bcf2 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,7 +4,7 @@ import messages from './common/i18n';
 import EditorPage from './editor/Editor.vue';
 import ExplorePage from './explore/Explore.vue';
 import ViewPage from './editor/View.vue';
-import {store} from './common/store';
+import { store } from './common/store';
 import VueScrollactive from 'vue-scrollactive';
 
 Vue.use(VueScrollactive);
@@ -19,35 +19,36 @@ Vue.use(VueScrollactive);
  * @param {string} [option.version]
  */
 export function init(el, option) {
-    const i18n = new VueI18n({
-        locale: option.locale,
-        fallbackLocale: 'en',
-        messages
-    });
-    store.cdnRoot = option.cdnRoot;
-    store.version = option.version;
-    store.locale = option.locale || 'en';
+  const i18n = new VueI18n({
+    locale: option.locale,
+    fallbackLocale: 'en',
+    messages
+  });
+  store.cdnRoot = option.cdnRoot;
+  store.version = option.version;
+  store.locale = option.locale || 'en';
 
+  if (typeof el === 'string') {
+    el = document.querySelector(el);
+  }
+  if (!el) {
+    throw new Error("Can't find el.");
+  }
 
-    if (typeof el === 'string') {
-        el = document.querySelector(el);
-    }
-    if (!el) {
-        throw new Error('Can\'t find el.');
-    }
+  const container = document.createElement('div');
+  el.appendChild(container);
 
-    const container = document.createElement('div');
-    el.appendChild(container);
-
-    new Vue({
-        i18n,
-        el: container,
-        render: h => {
-            return h(({
-                editor: EditorPage,
-                explore: ExplorePage,
-                view: ViewPage
-            })[option.page] || ExplorePage);
-        }
-    });
-}
\ No newline at end of file
+  new Vue({
+    i18n,
+    el: container,
+    render: (h) => {
+      return h(
+        {
+          editor: EditorPage,
+          explore: ExplorePage,
+          view: ViewPage
+        }[option.page] || ExplorePage
+      );
+    }
+  });
+}
diff --git a/src/style/color.scss b/src/style/color.scss
index fda7835..62ed085 100644
--- a/src/style/color.scss
+++ b/src/style/color.scss
@@ -1,38 +1,36 @@
-$clr-primary:        #293c55;
-$clr-primary-light:  #849dbe;
-$clr-primary-dark:   #162436;
+$clr-primary: #293c55;
+$clr-primary-light: #849dbe;
+$clr-primary-dark: #162436;
 $clr-primary-darker: #0e151f;
 
-$clr-primary-home:   #333743;
-$clr-text-home:      #9297A3;
+$clr-primary-home: #333743;
+$clr-text-home: #9297a3;
 
-$clr-secondary:      #a9334c;
+$clr-secondary: #a9334c;
 $clr-secondary-dark: darken($clr-secondary, 10%);
 
-$clr-contrast:       #e43c59;
-$clr-contrast-dark:  #bf465b;
+$clr-contrast: #e43c59;
+$clr-contrast-dark: #bf465b;
 
-$clr-darkest:        #000;
-$clr-darker:         #333;
-$clr-dark:           #666;
-$clr-light:          #999;
-$clr-lighter:        #eee;
-$clr-lightest:       #f9f9f9;
+$clr-darkest: #000;
+$clr-darker: #333;
+$clr-dark: #666;
+$clr-light: #999;
+$clr-lighter: #eee;
+$clr-lightest: #f9f9f9;
 
-$clr-thirdary:       #40A7DC;
+$clr-thirdary: #40a7dc;
 $clr-thirdary-light: lighten($clr-thirdary, 10%);
 
 $clr-border: rgba(78, 97, 118, 0.45);
 $clr-border-light: rgba(78, 97, 118, 0.25);
 
-$clr-gray:           #ccc;
-$clr-gray-dark:      #999;
-$clr-gray-light:     #f3f3f3;
-
-$clr-warn:           #f93;
-$clr-error:          $clr-contrast;
-
+$clr-gray: #ccc;
+$clr-gray-dark: #999;
+$clr-gray-light: #f3f3f3;
 
+$clr-warn: #f93;
+$clr-error: $clr-contrast;
 
 $clr-bg: #f3f4fa;
 $clr-text: $clr-dark;
diff --git a/src/style/config.xl.scss b/src/style/config.xl.scss
index 678b094..37e7de6 100644
--- a/src/style/config.xl.scss
+++ b/src/style/config.xl.scss
@@ -18,607 +18,676 @@
    Fix visible-lg and hidden-lg for resolutions over 1600px
    Remove if don't want to use
    ========================================================================== */
-   @media (min-width: 1600px) {
-    .visible-lg {
-      display: none !important;
-    }
-    .hidden-lg {
-      display: block !important;
-    }
-    table.hidden-lg {
-      display: table;
-    }
-    tr.hidden-lg {
-      display: table-row !important;
-    }
-    th.hidden-lg,
-    td.hidden-lg {
-      display: table-cell !important;
-    }
+@media (min-width: 1600px) {
+  .visible-lg {
+    display: none !important;
+  }
+  .hidden-lg {
+    display: block !important;
+  }
+  table.hidden-lg {
+    display: table;
   }
+  tr.hidden-lg {
+    display: table-row !important;
+  }
+  th.hidden-lg,
+  td.hidden-lg {
+    display: table-cell !important;
+  }
+}
 
-  /* ==========================================================================
+/* ==========================================================================
      Set containers fixed sizes for >1600px, >1920px, >2560px
      Remove all if don't want to use big fixed sizes for all blocks
      You still can use cols with .container-fluid blocks
      ========================================================================== */
-  @media (min-width: 1600px) {
-    .container {
-      width: 1530px;
-    }
+@media (min-width: 1600px) {
+  .container {
+    width: 1530px;
   }
-  @media (min-width: 1920px) {
-    .container {
-      width: 1830px;
-    }
+}
+@media (min-width: 1920px) {
+  .container {
+    width: 1830px;
   }
-  @media (min-width: 2560px) {
-    .container {
-      width: 2490px;
-    }
+}
+@media (min-width: 2560px) {
+  .container {
+    width: 2490px;
   }
+}
 
-  /* ==========================================================================
+/* ==========================================================================
      col-xl, col-xxl, col-xxxl setup.
      Don't remove anything below this line
      ========================================================================== */
 
-  .col-xl-1, .col-xxl-1, .col-xxxl-1, .col-xl-2, .col-xxl-2, .col-md-2, .col-xxxl-2, .col-xl-3, .col-xxl-3, .col-md-3, .col-xxxl-3, .col-xl-4, .col-xxl-4, .col-md-4, .col-xxxl-4, .col-xl-5, .col-xxl-5, .col-md-5, .col-xxxl-5, .col-xl-6, .col-xxl-6, .col-md-6, .col-xxxl-6, .col-xl-7, .col-xxl-7, .col-md-7, .col-xxxl-7, .col-xl-8, .col-xxl-8, .col-md-8, .col-xxxl-8, .col-xl-9, .col-xxl-9, .col-md-9, .col-xxxl-9, .col-xl-10, .col-xxl-100, .col-xxxl-10, .col-xl-11, .col-xxl-111, .col-xxxl-11 [...]
-    position: relative;
-    min-height: 1px;
-    padding-right: 15px;
-    padding-left: 15px;
-  }
-  @media (min-width: 1600px) {
-    .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
-      float: left;
-    }
-    .col-xl-12 {
-      width: 100%;
-    }
-    .col-xl-11 {
-      width: 91.66666667%;
-    }
-    .col-xl-10 {
-      width: 83.33333333%;
-    }
-    .col-xl-9 {
-      width: 75%;
-    }
-    .col-xl-8 {
-      width: 66.66666667%;
-    }
-    .col-xl-7 {
-      width: 58.33333333%;
-    }
-    .col-xl-6 {
-      width: 50%;
-    }
-    .col-xl-5 {
-      width: 41.66666667%;
-    }
-    .col-xl-4 {
-      width: 33.33333333%;
-    }
-    .col-xl-3 {
-      width: 25%;
-    }
-    .col-xl-2 {
-      width: 16.66666667%;
-    }
-    .col-xl-1 {
-      width: 8.33333333%;
-    }
-    .col-xl-pull-12 {
-      right: 100%;
-    }
-    .col-xl-pull-11 {
-      right: 91.66666667%;
-    }
-    .col-xl-pull-10 {
-      right: 83.33333333%;
-    }
-    .col-xl-pull-9 {
-      right: 75%;
-    }
-    .col-xl-pull-8 {
-      right: 66.66666667%;
-    }
-    .col-xl-pull-7 {
-      right: 58.33333333%;
-    }
-    .col-xl-pull-6 {
-      right: 50%;
-    }
-    .col-xl-pull-5 {
-      right: 41.66666667%;
-    }
-    .col-xl-pull-4 {
-      right: 33.33333333%;
-    }
-    .col-xl-pull-3 {
-      right: 25%;
-    }
-    .col-xl-pull-2 {
-      right: 16.66666667%;
-    }
-    .col-xl-pull-1 {
-      right: 8.33333333%;
-    }
-    .col-xl-pull-0 {
-      right: 0;
-    }
-    .col-xl-push-12 {
-      left: 100%;
-    }
-    .col-xl-push-11 {
-      left: 91.66666667%;
-    }
-    .col-xl-push-10 {
-      left: 83.33333333%;
-    }
-    .col-xl-push-9 {
-      left: 75%;
-    }
-    .col-xl-push-8 {
-      left: 66.66666667%;
-    }
-    .col-xl-push-7 {
-      left: 58.33333333%;
-    }
-    .col-xl-push-6 {
-      left: 50%;
-    }
-    .col-xl-push-5 {
-      left: 41.66666667%;
-    }
-    .col-xl-push-4 {
-      left: 33.33333333%;
-    }
-    .col-xl-push-3 {
-      left: 25%;
-    }
-    .col-xl-push-2 {
-      left: 16.66666667%;
-    }
-    .col-xl-push-1 {
-      left: 8.33333333%;
-    }
-    .col-xl-push-0 {
-      left: 0;
-    }
-    .col-xl-offset-12 {
-      margin-left: 100%;
-    }
-    .col-xl-offset-11 {
-      margin-left: 91.66666667%;
-    }
-    .col-xl-offset-10 {
-      margin-left: 83.33333333%;
-    }
-    .col-xl-offset-9 {
-      margin-left: 75%;
-    }
-    .col-xl-offset-8 {
-      margin-left: 66.66666667%;
-    }
-    .col-xl-offset-7 {
-      margin-left: 58.33333333%;
-    }
-    .col-xl-offset-6 {
-      margin-left: 50%;
-    }
-    .col-xl-offset-5 {
-      margin-left: 41.66666667%;
-    }
-    .col-xl-offset-4 {
-      margin-left: 33.33333333%;
-    }
-    .col-xl-offset-3 {
-      margin-left: 25%;
-    }
-    .col-xl-offset-2 {
-      margin-left: 16.66666667%;
-    }
-    .col-xl-offset-1 {
-      margin-left: 8.33333333%;
-    }
-    .col-xl-offset-0 {
-      margin-left: 0;
-    }
+.col-xl-1,
+.col-xxl-1,
+.col-xxxl-1,
+.col-xl-2,
+.col-xxl-2,
+.col-md-2,
+.col-xxxl-2,
+.col-xl-3,
+.col-xxl-3,
+.col-md-3,
+.col-xxxl-3,
+.col-xl-4,
+.col-xxl-4,
+.col-md-4,
+.col-xxxl-4,
+.col-xl-5,
+.col-xxl-5,
+.col-md-5,
+.col-xxxl-5,
+.col-xl-6,
+.col-xxl-6,
+.col-md-6,
+.col-xxxl-6,
+.col-xl-7,
+.col-xxl-7,
+.col-md-7,
+.col-xxxl-7,
+.col-xl-8,
+.col-xxl-8,
+.col-md-8,
+.col-xxxl-8,
+.col-xl-9,
+.col-xxl-9,
+.col-md-9,
+.col-xxxl-9,
+.col-xl-10,
+.col-xxl-100,
+.col-xxxl-10,
+.col-xl-11,
+.col-xxl-111,
+.col-xxxl-11,
+.col-xl-12,
+.col-xxl-122,
+.col-xxxl-12 {
+  position: relative;
+  min-height: 1px;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+@media (min-width: 1600px) {
+  .col-xl-1,
+  .col-xl-2,
+  .col-xl-3,
+  .col-xl-4,
+  .col-xl-5,
+  .col-xl-6,
+  .col-xl-7,
+  .col-xl-8,
+  .col-xl-9,
+  .col-xl-10,
+  .col-xl-11,
+  .col-xl-12 {
+    float: left;
   }
-
-  @media (min-width: 1920px) {
-    .col-xxl-1, .col-xxl-2, .col-xxl-3, .col-xxl-4, .col-xxl-5, .col-xxl-6, .col-xxl-7, .col-xxl-8, .col-xxl-9, .col-xxl-10, .col-xxl-11, .col-xxl-12 {
-      float: left;
-    }
-    .col-xxl-12 {
-      width: 100%;
-    }
-    .col-xxl-11 {
-      width: 91.66666667%;
-    }
-    .col-xxl-10 {
-      width: 83.33333333%;
-    }
-    .col-xxl-9 {
-      width: 75%;
-    }
-    .col-xxl-8 {
-      width: 66.66666667%;
-    }
-    .col-xxl-7 {
-      width: 58.33333333%;
-    }
-    .col-xxl-6 {
-      width: 50%;
-    }
-    .col-xxl-5 {
-      width: 41.66666667%;
-    }
-    .col-xxl-4 {
-      width: 33.33333333%;
-    }
... 2617 lines suppressed ...

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org