Merge branch 'gh-pages' of https://github.com/apache/echarts-examples into gh-pages
diff --git a/public/examples/ts/data-transform-aggregate.js b/public/examples/ts/data-transform-aggregate.js
index 52a0999..1af5461 100644
--- a/public/examples/ts/data-transform-aggregate.js
+++ b/public/examples/ts/data-transform-aggregate.js
@@ -15,7 +15,7 @@
 });
 
 function run(_rawData) {
-  echarts.registerTransform(window.ecSimpleTransform.aggregate);
+  echarts.registerTransform(ecSimpleTransform.aggregate);
 
   option = {
     dataset: [
diff --git a/public/examples/ts/gl/bar3d-noise-modified-from-marpi-demo.js b/public/examples/ts/gl/bar3d-noise-modified-from-marpi-demo.js
index a0db195..68c870c 100644
--- a/public/examples/ts/gl/bar3d-noise-modified-from-marpi-demo.js
+++ b/public/examples/ts/gl/bar3d-noise-modified-from-marpi-demo.js
@@ -9,8 +9,6 @@
 ).done(function () {
   var simplex = new SimplexNoise();
 
-  window.onresize = myChart.resize;
-
   var UPDATE_DURATION = 1000;
 
   function initVisualizer() {
diff --git a/src/common/route.js b/src/common/route.js
index 0dd04ca..1da66ec 100644
--- a/src/common/route.js
+++ b/src/common/route.js
@@ -1,24 +1,9 @@
 export function getURL(params) {
-  const searchUrlParts = [];
-  for (let key in params) {
-    if (params.hasOwnProperty(key)) {
-      let part = key;
-      if (params[key] != null) {
-        part += '=' + params[key];
-      }
-      searchUrlParts.push(part);
-    }
-  }
-  const searchUrl = searchUrlParts.join('&');
-
-  return (
-    location.protocol +
-    '//' +
-    location.hostname +
-    (location.port ? ':' + location.port : '') +
-    location.pathname +
-    (searchUrl ? '?' + searchUrl : '')
+  const url = new URL(location.href);
+  Object.entries(params).forEach(([k, v]) =>
+    v == null ? url.searchParams.delete(k) : url.searchParams.set(k, v)
   );
+  return url.toString();
 }
 
 export function gotoURL(params, pushHistory) {
diff --git a/src/common/store.js b/src/common/store.js
index 83f1f02..533e730 100644
--- a/src/common/store.js
+++ b/src/common/store.js
@@ -16,7 +16,12 @@
 
   darkMode: URL_PARAMS.theme === 'dark',
   enableDecal: 'decal' in URL_PARAMS,
-  renderer: URL_PARAMS.renderer || 'canvas',
+  renderer: (() => {
+    const renderer = URL_PARAMS.renderer && URL_PARAMS.renderer.toLowerCase();
+    return renderer && ['canvas', 'svg'].includes(renderer)
+      ? renderer
+      : 'canvas';
+  })(),
 
   typeCheck:
     getExampleConfig() &&
@@ -62,12 +67,16 @@
 
 const LOCAL_EXAMPLE_CODE_STORE_KEY = 'echarts-examples-code';
 
+// for sharing URL
+export const CODE_CHANGED_FLAG = '__CODE_CHANGED__';
+
 export function saveExampleCodeToLocal() {
   localStorage.setItem(
     LOCAL_EXAMPLE_CODE_STORE_KEY,
     compressStr(
       JSON.stringify({
         code: store.sourceCode,
+        codeModified: store.initialCode !== store.sourceCode,
         lang: store.typeCheck ? 'ts' : 'js'
       })
     )
@@ -92,6 +101,10 @@
   const localCode = loadExampleCodeFromLocal();
   if (localCode) {
     clearLocalExampleCode();
+    // for sharing URL
+    if (localCode.codeModified) {
+      store.initialCode = CODE_CHANGED_FLAG;
+    }
     return Promise.resolve(localCode.code);
   }
   return new Promise((resolve, reject) => {
diff --git a/src/editor/Editor.vue b/src/editor/Editor.vue
index 6223051..a14442e 100644
--- a/src/editor/Editor.vue
+++ b/src/editor/Editor.vue
@@ -196,7 +196,8 @@
   store,
   loadExampleCode,
   parseSourceCode,
-  getExampleConfig
+  getExampleConfig,
+  CODE_CHANGED_FLAG
 } from '../common/store';
 import { collectDeps, buildExampleCode } from '../../common/buildCode';
 import { gotoURL } from '../common/route';
@@ -254,7 +255,10 @@
     } else {
       loadExampleCode().then((code) => {
         // Only set the code in editor. editor will sync to the store.
-        store.initialCode = this.initialCode = parseSourceCode(code);
+        this.initialCode = parseSourceCode(code);
+        if (store.initialCode !== CODE_CHANGED_FLAG) {
+          store.initialCode = this.initialCode;
+        }
       });
 
       window.addEventListener('mousemove', (e) => {
@@ -384,24 +388,14 @@
     changeLang(lang) {
       if ((URL_PARAMS.lang || 'js').toLowerCase() !== lang) {
         if (!this.initialCode || store.sourceCode === this.initialCode) {
-          gotoURL(
-            Object.assign({}, URL_PARAMS, {
-              lang
-            })
-          );
+          gotoURL({ lang });
         } else {
           this.$confirm(this.$t('editor.codeChangedConfirm'), '', {
             confirmButtonText: this.$t('editor.confirmButtonText'),
             cancelButtonText: this.$t('editor.cancelButtonText'),
             type: 'warning'
           })
-            .then(() => {
-              gotoURL(
-                Object.assign({}, URL_PARAMS, {
-                  lang
-                })
-              );
-            })
+            .then(() => gotoURL({ lang }))
             .catch(() => {});
         }
       }
diff --git a/src/editor/Preview.vue b/src/editor/Preview.vue
index e7f5a08..ba27a91 100644
--- a/src/editor/Preview.vue
+++ b/src/editor/Preview.vue
@@ -145,7 +145,7 @@
 import { createSandbox } from './sandbox';
 import debounce from 'lodash/debounce';
 import { download } from './downloadExample';
-import { gotoURL } from '../common/route';
+import { gotoURL, getURL } from '../common/route';
 import { gt } from 'semver';
 
 const example = getExampleConfig();
@@ -347,6 +347,15 @@
     },
     isNightlyVersion() {
       return store.echartsVersion && store.echartsVersion.indexOf('dev') > -1;
+    },
+    toolOptions() {
+      const isCanvas = store.renderer === 'canvas';
+      return {
+        renderer: isCanvas ? null : store.renderer,
+        useDirtyRect: store.useDirtyRect && isCanvas ? 1 : null,
+        decal: store.enableDecal ? 1 : null,
+        theme: store.darkMode ? 'dark' : null
+      };
     }
   },
 
@@ -361,17 +370,12 @@
         }
       }
     },
-    'shared.renderer'() {
-      this.refresh();
-    },
-    'shared.darkMode'() {
-      this.refresh();
-    },
-    'shared.enableDecal'() {
-      this.refresh();
-    },
-    'shared.useDirtyRect'() {
-      this.refresh();
+    toolOptions: {
+      handler(n) {
+        this.refresh();
+        gotoURL(n, true);
+      },
+      deep: true
     },
     isNightlyVersion: {
       handler(val) {
@@ -409,12 +413,13 @@
         );
     },
     share() {
-      let shareURL = new URL(location.href);
+      const params = {};
       if (store.initialCode !== store.sourceCode) {
-        shareURL.searchParams.set('code', compressStr(store.sourceCode));
+        params.code = compressStr(store.sourceCode);
       }
+      const sharableURL = getURL(params);
       navigator.clipboard
-        .writeText(shareURL.toString())
+        .writeText(sharableURL)
         .then(() => {
           this.$message.closeAll();
           this.$message({
@@ -426,7 +431,7 @@
         // PENDING
         .catch((e) => {
           console.error('failed to write share url to the clipboard', e);
-          window.open(shareURL, '_blank');
+          window.open(sharableURL, '_blank');
         });
     },
     getOption() {
@@ -434,23 +439,11 @@
     },
     changeVersion() {
       saveExampleCodeToLocal();
-      setTimeout(() => {
-        gotoURL(
-          Object.assign({}, URL_PARAMS, {
-            version: store.echartsVersion
-          })
-        );
-      });
+      setTimeout(() => gotoURL({ version: store.echartsVersion }));
     },
     changeRandomSeed() {
       updateRandomSeed();
-      gotoURL(
-        {
-          ...URL_PARAMS,
-          random: store.randomSeed
-        },
-        true
-      );
+      gotoURL({ random: store.randomSeed }, true);
       this.run();
     },
     // hasEditorError() {
diff --git a/src/editor/sandbox/setup.js b/src/editor/sandbox/setup.js
index 099091d..4ed795e 100644
--- a/src/editor/sandbox/setup.js
+++ b/src/editor/sandbox/setup.js
@@ -184,7 +184,10 @@
             console.error('failed to show debug dirty rect', e);
           }
         }
-        window.addEventListener('resize', chartInstance.resize);
+        window.addEventListener('resize', () => {
+          chartInstance.resize();
+          echarts.util.isFunction(appEnv.onresize) && appEnv.onresize();
+        });
         wrapChartMethods(chartInstance);
       }
 
@@ -192,7 +195,7 @@
       clearTimers();
       clearChartEvents(chartInstance);
       // Reset
-      appEnv.config = null;
+      appEnv = {};
       appStore = store;
 
       try {
diff --git a/src/explore/ExampleCard.vue b/src/explore/ExampleCard.vue
index 97552ee..b2876a9 100644
--- a/src/explore/ExampleCard.vue
+++ b/src/explore/ExampleCard.vue
@@ -28,7 +28,7 @@
         >
       </div>
       <div>
-        <div class="example-title">{{ title }}</div>
+        <div class="example-title" :title="title">{{ title }}</div>
         <div class="example-subtitle" v-if="showSubtitle">{{ subtitle }}</div>
       </div>
     </div>
@@ -68,18 +68,12 @@
       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');
-      }
+      example.isGL && hash.push('gl=1');
+      exampleTheme && hash.push('theme=' + exampleTheme);
+      'local' in URL_PARAMS && hash.push('local=1');
+      'debug' in URL_PARAMS && hash.push('debug=1');
+      'useDirtyRect' in URL_PARAMS && hash.push('useDirtyRect=1');
+      URL_PARAMS.renderer && hash.push('renderer=' + URL_PARAMS.renderer);
       return './editor.html?' + hash.join('&');
     },