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 2020/06/18 06:42:25 UTC

[incubator-echarts-doc] branch live-example updated: example: add more UI config, support custom symbol upload

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

shenyi pushed a commit to branch live-example
in repository https://gitbox.apache.org/repos/asf/incubator-echarts-doc.git


The following commit(s) were added to refs/heads/live-example by this push:
     new 1335a40  example: add more UI config, support custom symbol upload
1335a40 is described below

commit 1335a408dd7ec785e26b2b196d083324380c2b4d
Author: pissang <bm...@gmail.com>
AuthorDate: Thu Jun 18 14:42:07 2020 +0800

    example: add more UI config, support custom symbol upload
---
 en/option/component/radar.md            |    4 -
 src/components/OptionControl.vue        |    5 +-
 src/controls/ControlIcon.vue            |  108 +++
 src/dep/flatten.js                      | 1175 +++++++++++++++++++++++++++++++
 src/i18n.js                             |    6 +
 tool/md2json.js                         |    5 +-
 zh/option/component/angle-axis.md       |   71 ++
 zh/option/component/axis-common.md      |    2 +-
 zh/option/component/data-zoom-inside.md |   48 +-
 zh/option/component/data-zoom-slider.md |   51 +-
 zh/option/component/data-zoom.md        |   21 +-
 zh/option/component/legend.md           |    4 +
 zh/option/component/radar.md            |   54 +-
 zh/option/partial/symbol.md             |    4 +-
 zh/option/series/pie.md                 |   23 +-
 zh/option/series/radar.md               |   18 +-
 16 files changed, 1545 insertions(+), 54 deletions(-)

diff --git a/en/option/component/radar.md b/en/option/component/radar.md
index 540a3c3..9960c9a 100644
--- a/en/option/component/radar.md
+++ b/en/option/component/radar.md
@@ -53,10 +53,6 @@ formatter: function (value, indicator) {
 
 Distance between the indicator's name and axis.
 
-## axisType(string) = 'value'
-{{ use: partial-version(version = "4.5.0") }}
-{{ use: partial-axis-type-content() }}
-
 ## splitNumber(number) = 5
 
 Segments of indicator axis.
diff --git a/src/components/OptionControl.vue b/src/components/OptionControl.vue
index 27ffd81..c6a85d8 100644
--- a/src/components/OptionControl.vue
+++ b/src/components/OptionControl.vue
@@ -19,6 +19,8 @@ import ControlPercent from '../controls/ControlPercent.vue';
 import {store, changeOption} from '../store';
 import ControlPercentVector from '../controls/ControlPercentVector.vue';
 import ControlText from '../controls/ControlText.vue';
+import ControlIcon from '../controls/ControlIcon.vue';
+
 
 const uiComponentMap = {
     boolean: ControlBoolean,
@@ -30,7 +32,8 @@ const uiComponentMap = {
     angle: ControlNumber,
     percent: ControlPercent,
     percentvector: ControlPercentVector,
-    text: ControlText
+    text: ControlText,
+    icon: ControlIcon
 };
 
 const uiComponentDefault = {
diff --git a/src/controls/ControlIcon.vue b/src/controls/ControlIcon.vue
new file mode 100644
index 0000000..473a574
--- /dev/null
+++ b/src/controls/ControlIcon.vue
@@ -0,0 +1,108 @@
+<template>
+<div class="control-icon">
+    <el-select size="mini" v-model="innerValue" @change="onValueChange">
+        <el-option v-for="item in optionsArr"
+            :key="item"
+            :value="item"
+        >{{item}}</el-option>
+    </el-select>
+    <el-button size="mini" type="primary" @click="chooseFile">{{$t('example.upload')}}</el-button>
+</div>
+</template>
+
+<script>
+
+import {flatten} from '../dep/flatten';
+
+export function parseXML(svgStr) {
+    const parser = new DOMParser();
+    const svg = parser.parseFromString(svgStr, 'text/xml');
+    let svgNode = svg;
+    // Document node. If using $.get, doc node may be input.
+    if (svgNode.nodeType === 9) {
+        svgNode = svgNode.firstChild;
+    }
+    // nodeName of <!DOCTYPE svg> is also 'svg'.
+    while (svgNode.nodeName.toLowerCase() !== 'svg' || svgNode.nodeType !== 1) {
+        svgNode = svgNode.nextSibling;
+    }
+
+    return svgNode;
+}
+
+export default {
+
+    props: ['value'],
+
+    computed: {
+        optionsArr() {
+            return ['circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none']
+        },
+    },
+
+    data() {
+        return {
+            innerValue: this.value
+        }
+    },
+
+    watch: {
+        value(val) {
+            this.innerValue = val;
+        }
+    },
+
+    methods: {
+        onValueChange() {
+            this.$emit('change', this.innerValue);
+        },
+        chooseFile() {
+            const input  = document.createElement('input');
+            input.type = 'file';
+            input.accept= '.jpg, .jpeg, .png, .svg'
+            input.addEventListener('change',  (e) => {
+                const file = e.target.files[0];
+                if (!file) {
+                    return;
+                }
+                if (file.name.endsWith('.svg')) {
+                    // read path
+                    // Use image
+                    const fileReader = new FileReader();
+                    fileReader.addEventListener('load', () => {
+                        const svgStr = fileReader.result;
+                        const svg = parseXML(svgStr);
+                        try {
+                            flatten(svg);
+                        }
+                        catch (e) {
+                            console.error('Unexpected error happens when handling the SVG.');
+                            console.error(e.toString());
+                        }
+
+                        const paths = svg.querySelectorAll('path');
+                        let defs = [];
+                        for (let i = 0; i < paths.length; i++) {
+                            defs.push(paths[i].getAttribute('d'));
+                        }
+                        this.$emit('change', 'path://' + defs.join(' '));
+                    });
+                    fileReader.readAsText(file);
+                }
+                else {
+                    // Use image
+                    const fileReader = new FileReader();
+                    fileReader.addEventListener('load', () => {
+                        this.$emit('change', 'image://' + fileReader.result);
+                    });
+                    fileReader.readAsDataURL(file);
+                }
+            });
+            input.click();
+        }
+    }
+}
+</script>
+
+<style lang="scss">
+</style>
\ No newline at end of file
diff --git a/src/dep/flatten.js b/src/dep/flatten.js
new file mode 100644
index 0000000..2357a78
--- /dev/null
+++ b/src/dep/flatten.js
@@ -0,0 +1,1175 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2014 Timo (https://github.com/timo22345)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+Usage example: http://jsfiddle.net/Nv78L/1/embedded/result/
+
+Basic usage: flatten(document.getElementById('svg'));
+
+What it does: Flattens elements (converts elements to paths and flattens transformations).
+If the argument element (whose id is above 'svg') has children, or it's descendants has children,
+these children elements are flattened also.
+
+If you want to modify path coordinates using non-affine methods (eg. perspective distort),
+you can convert all segments to cubic curves using:
+
+flatten(document.getElementById('svg'), true);
+
+There are also arguments 'toAbsolute' (convert coordinates to absolute) and 'dec',
+number of digits after decimal separator.
+*/
+
+
+SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) {
+    return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM());
+};
+  var p2s = /,?([achlmqrstvxz]),?/gi;
+  var convertToString = function (arr)
+  {
+    return arr.join(',').replace(p2s, '$1');
+  };
+
+  // Flattens transformations of element or it's children and sub-children
+  // elem: DOM element
+  // toCubics: converts all segments to cubics
+  // toAbsolute: converts all segments to Absolute
+  // dec: number of digits after decimal separator
+  // Returns: no return value
+export function flatten(elem, toCubics, toAbsolute, rectAsArgs, dec)
+  {
+    if (!elem) return;
+    if (typeof (rectAsArgs) == 'undefined') rectAsArgs = false;
+    if (typeof (toCubics) == 'undefined') toCubics = false;
+    if (typeof (toAbsolute) == 'undefined') toAbsolute = false;
+    if (typeof (dec) == 'undefined') dec = false;
+
+    if (elem && elem.children && elem.children.length)
+    {
+      for (var i = 0, ilen = elem.children.length; i < ilen; i++)
+      {
+        //console.log(elem.children[i]);
+        flatten(elem.children[i], toCubics, toAbsolute, rectAsArgs, dec);
+      }
+      elem.removeAttribute('transform');
+      return;
+    }
+    if (!(elem instanceof SVGCircleElement ||
+          elem instanceof SVGRectElement ||
+          elem instanceof SVGEllipseElement ||
+          elem instanceof SVGLineElement ||
+          elem instanceof SVGPolygonElement ||
+          elem instanceof SVGPolylineElement ||
+          elem instanceof SVGPathElement)) return;
+
+    var path_elem = convertToPath(elem, rectAsArgs);
+    //console.log('path_elem', $(path_elem).wrap('<div />').parent().html() );
+    //$(path_elem).unwrap();
+
+    if (!path_elem || path_elem.getAttribute(d) == '') return 'M 0 0';
+
+    // Rounding coordinates to dec decimals
+    if (dec || dec === 0)
+    {
+      if (dec > 15) dec = 15;
+      else if (dec < 0) dec = 0;
+    }
+    else dec = false;
+
+    function r(num)
+    {
+      if (dec !== false) return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
+      else return num;
+    }
+
+    var arr;
+    //var pathDOM = path_elem.node;
+    var pathDOM = path_elem;
+    var d = pathDOM.getAttribute('d').trim();
+
+    // If you want to retain current path commans, set toCubics to false
+    if (!toCubics)
+    { // Set to false to prevent possible re-normalization.
+      arr = parsePathString(d); // str to array
+      var arr_orig = arr;
+      arr = pathToAbsolute(arr); // mahvstcsqz -> uppercase
+    }
+    // If you want to modify path data using nonAffine methods,
+    // set toCubics to true
+    else
+    {
+      arr = path2curve(d); // mahvstcsqz -> MC
+      var arr_orig = arr;
+    }
+    var svgDOM = pathDOM.ownerSVGElement;
+
+    // Get the relation matrix that converts path coordinates
+    // to SVGroot's coordinate space
+    var matrix = pathDOM.getTransformToElement(svgDOM);
+
+    // The following code can bake transformations
+    // both normalized and non-normalized data
+    // Coordinates have to be Absolute in the following
+    var i = 0,
+      j, m = arr.length,
+      letter = '',
+      letter_orig = '',
+      x = 0,
+      y = 0,
+      point, newcoords = [],
+      newcoords_orig = [],
+      pt = svgDOM.createSVGPoint(),
+      subpath_start = {}, prevX = 0,
+      prevY = 0;
+    subpath_start.x = null;
+    subpath_start.y = null;
+    for (; i < m; i++)
+    {
+      letter = arr[i][0].toUpperCase();
+      letter_orig = arr_orig[i][0];
+      newcoords[i] = [];
+      newcoords[i][0] = arr[i][0];
+
+      if (letter == 'A')
+      {
+        x = arr[i][6];
+        y = arr[i][7];
+
+        pt.x = arr[i][6];
+        pt.y = arr[i][7];
+        newcoords[i] = arc_transform(arr[i][1], arr[i][2], arr[i][3], arr[i][4], arr[i][5], pt, matrix);
+        // rounding arc parameters
+        // x,y are rounded normally
+        // other parameters at least to 5 decimals
+        // because they affect more than x,y rounding
+        newcoords[i][1] = newcoords[i][1]; //rx
+        newcoords[i][2] = newcoords[i][2]; //ry
+        newcoords[i][3] = newcoords[i][3]; //x-axis-rotation
+        newcoords[i][6] = newcoords[i][6]; //x
+        newcoords[i][7] = newcoords[i][7]; //y
+      }
+      else if (letter != 'Z')
+      {
+        // parse other segs than Z and A
+        for (j = 1; j < arr[i].length; j = j + 2)
+        {
+          if (letter == 'V') y = arr[i][j];
+          else if (letter == 'H') x = arr[i][j];
+          else
+          {
+            x = arr[i][j];
+            y = arr[i][j + 1];
+          }
+          pt.x = x;
+          pt.y = y;
+          point = pt.matrixTransform(matrix);
+
+          if (letter == 'V' || letter == 'H')
+          {
+            newcoords[i][0] = 'L';
+            newcoords[i][j] = point.x;
+            newcoords[i][j + 1] = point.y;
+          }
+          else
+          {
+            newcoords[i][j] = point.x;
+            newcoords[i][j + 1] = point.y;
+          }
+        }
+      }
+      if ((letter != 'Z' && subpath_start.x === null) || letter == 'M')
+      {
+        subpath_start.x = x;
+        subpath_start.y = y;
+      }
+      if (letter == 'Z')
+      {
+        x = subpath_start.x;
+        y = subpath_start.y;
+      }
+    }
+    // Convert all that was relative back to relative
+    // This could be combined to above, but to make code more readable
+    // this is made separately.
+    var prevXtmp = 0;
+    var prevYtmp = 0;
+    subpath_start.x = '';
+    for (i = 0; i < newcoords.length; i++)
+    {
+      letter_orig = arr_orig[i][0];
+      if (letter_orig == 'A' || letter_orig == 'M' || letter_orig == 'L' || letter_orig == 'C' || letter_orig == 'S' || letter_orig == 'Q' || letter_orig == 'T' || letter_orig == 'H' || letter_orig == 'V')
+      {
+        var len = newcoords[i].length;
+        var lentmp = len;
+        if (letter_orig == 'A')
+        {
+          newcoords[i][6] = r(newcoords[i][6]);
+          newcoords[i][7] = r(newcoords[i][7]);
+        }
+        else
+        {
+          lentmp--;
+          while (--lentmp) newcoords[i][lentmp] = r(newcoords[i][lentmp]);
+        }
+        prevX = newcoords[i][len - 2];
+        prevY = newcoords[i][len - 1];
+      }
+      else
+      if (letter_orig == 'a')
+      {
+        prevXtmp = newcoords[i][6];
+        prevYtmp = newcoords[i][7];
+        newcoords[i][0] = letter_orig;
+        newcoords[i][6] = r(newcoords[i][6] - prevX);
+        newcoords[i][7] = r(newcoords[i][7] - prevY);
+        prevX = prevXtmp;
+        prevY = prevYtmp;
+      }
+      else
+      if (letter_orig == 'm' || letter_orig == 'l' || letter_orig == 'c' || letter_orig == 's' || letter_orig == 'q' || letter_orig == 't' || letter_orig == 'h' || letter_orig == 'v')
+      {
+        var len = newcoords[i].length;
+        prevXtmp = newcoords[i][len - 2];
+        prevYtmp = newcoords[i][len - 1];
+        for (j = 1; j < len; j = j + 2)
+        {
+          if (letter_orig == 'h' || letter_orig == 'v')
+            newcoords[i][0] = 'l';
+          else newcoords[i][0] = letter_orig;
+          newcoords[i][j] = r(newcoords[i][j] - prevX);
+          newcoords[i][j + 1] = r(newcoords[i][j + 1] - prevY);
+        }
+        prevX = prevXtmp;
+        prevY = prevYtmp;
+      }
+      if ((letter_orig.toLowerCase() != 'z' && subpath_start.x == '') || letter_orig.toLowerCase() == 'm')
+      {
+        subpath_start.x = prevX;
+        subpath_start.y = prevY;
+      }
+      if (letter_orig.toLowerCase() == 'z')
+      {
+        prevX = subpath_start.x;
+        prevY = subpath_start.y;
+      }
+    }
+    if (toAbsolute) newcoords = pathToAbsolute(newcoords);
+    path_elem.setAttribute('d', convertToString(newcoords));
+    path_elem.removeAttribute('transform');
+  }
+
+  // Converts all shapes to path retaining attributes.
+  // oldElem - DOM element to be replaced by path. Can be one of the following:
+  //   ellipse, circle, path, line, polyline, polygon and rect.
+  // rectAsArgs - Boolean. If true, rect roundings will be as arcs. Otherwise as cubics.
+  // Return value: path element.
+  // Source: https://github.com/duopixel/Method-Draw/blob/master/editor/src/svgcanvas.js
+  // Modifications: Timo (https://github.com/timo22345)
+  function convertToPath(oldElem, rectAsArgs)
+  {
+    if (!oldElem) return;
+    // Create new path element
+    var path = document.createElementNS(oldElem.ownerSVGElement.namespaceURI, 'path');
+
+    // All attributes that path element can have
+    var attrs = ['requiredFeatures', 'requiredExtensions', 'systemLanguage', 'id', 'xml:base', 'xml:lang', 'xml:space', 'onfocusin', 'onfocusout', 'onactivate', 'onclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmousemove', 'onmouseout', 'onload', 'alignment-baseline', 'baseline-shift', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-ba [...]
+
+    // Copy attributes of oldElem to path
+    var attrName, attrValue;
+    for (var i = 0, ilen = attrs.length; i < ilen; i++)
+    {
+      var attrName = attrs[i];
+      var attrValue = oldElem.getAttribute(attrName);
+      if (attrValue) path.setAttribute(attrName, attrValue);
+    }
+
+    var d = '';
+
+    var valid = function (val)
+    {
+      return !(typeof (val) !== 'number' || val == Infinity || val < 0);
+    }
+
+    // Possibly the cubed root of 6, but 1.81 works best
+    var num = 1.81;
+    var tag = oldElem.tagName;
+    switch (tag)
+    {
+    case 'ellipse':
+    case 'circle':
+      var rx = +oldElem.getAttribute('rx'),
+        ry = +oldElem.getAttribute('ry'),
+        cx = +oldElem.getAttribute('cx'),
+        cy = +oldElem.getAttribute('cy');
+      if (tag == 'circle')
+      {
+        rx = ry = +oldElem.getAttribute('r');
+      }
+
+      d += convertToString([
+   ['M', (cx - rx), (cy)],
+   ['C', (cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)],
+   ['C', (cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)],
+   ['C', (cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)],
+   ['C', (cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)],
+   ['Z']
+  ]);
+      break;
+    case 'path':
+      d = oldElem.getAttribute('d');
+      break;
+    case 'line':
+      var x1 = oldElem.getAttribute('x1'),
+        y1 = oldElem.getAttribute('y1');
+      x2 = oldElem.getAttribute('x2');
+      y2 = oldElem.getAttribute('y2');
+      d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
+      break;
+    case 'polyline':
+      d = 'M' + oldElem.getAttribute('points');
+      break;
+    case 'polygon':
+      d = 'M' + oldElem.getAttribute('points') + 'Z';
+      break;
+    case 'rect':
+      var rx = +oldElem.getAttribute('rx'),
+        ry = +oldElem.getAttribute('ry'),
+        b = oldElem.getBBox(),
+        x = b.x,
+        y = b.y,
+        w = b.width,
+        h = b.height;
+
+      // Validity checks from http://www.w3.org/TR/SVG/shapes.html#RectElement:
+      // If neither ‘rx’ nor ‘ry’ are properly specified, then set both rx and ry to 0. (This will result in square corners.)
+      if (!valid(rx) && !valid(ry)) rx = ry = 0;
+      // Otherwise, if a properly specified value is provided for ‘rx’, but not for ‘ry’, then set both rx and ry to the value of ‘rx’.
+      else if (valid(rx) && !valid(ry)) ry = rx;
+      // Otherwise, if a properly specified value is provided for ‘ry’, but not for ‘rx’, then set both rx and ry to the value of ‘ry’.
+      else if (valid(ry) && !valid(rx)) rx = ry;
+      else
+      {
+        // If rx is greater than half of ‘width’, then set rx to half of ‘width’.
+        if (rx > w / 2) rx = w / 2;
+        // If ry is greater than half of ‘height’, then set ry to half of ‘height’.
+        if (ry > h / 2) ry = h / 2;
+      }
+
+      if (!rx && !ry)
+      {
+        d += convertToString([
+      ['M', x, y],
+      ['L', x + w, y],
+      ['L', x + w, y + h],
+      ['L', x, y + h],
+      ['L', x, y],
+      ['Z']
+     ]);
+      }
+      else if (rectAsArgs)
+      {
+        d += convertToString([
+      ['M', x + rx, y],
+      ['H', x + w - rx],
+      ['A', rx, ry, 0, 0, 1, x + w, y + ry],
+      ['V', y + h - ry],
+      ['A', rx, ry, 0, 0, 1, x + w - rx, y + h],
+      ['H', x + rx],
+      ['A', rx, ry, 0, 0, 1, x, y + h - ry],
+      ['V', y + ry],
+      ['A', rx, ry, 0, 0, 1, x + rx, y]
+      ]);
+      }
+      else
+      {
+        var num = 2.19;
+        if (!ry) ry = rx
+        d += convertToString([
+    ['M', x, y + ry],
+    ['C', x, y + ry / num, x + rx / num, y, x + rx, y],
+    ['L', x + w - rx, y],
+    ['C', x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry],
+    ['L', x + w, y + h - ry],
+    ['C', x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h],
+    ['L', x + rx, y + h],
+    ['C', x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry],
+    ['L', x, y + ry],
+    ['Z']
+     ]);
+      }
+      break;
+    default:
+      //path.parentNode.removeChild(path);
+      break;
+    }
+
+    if (d) path.setAttribute('d', d);
+
+    // Replace the current element with the converted one
+    oldElem.parentNode.replaceChild(path, oldElem);
+    return path;
+  };
+
+  // This is needed to flatten transformations of elliptical arcs
+  // Note! This is not needed if Raphael.path2curve is used
+  function arc_transform(a_rh, a_rv, a_offsetrot, large_arc_flag, sweep_flag, endpoint, matrix, svgDOM)
+  {
+    function NEARZERO(B)
+    {
+      if (Math.abs(B) < 0.0000000000000001) return true;
+      else return false;
+    }
+
+    var rh, rv, rot;
+
+    var m = []; // matrix representation of transformed ellipse
+    var s, c; // sin and cos helpers (the former offset rotation)
+    var A, B, C; // ellipse implicit equation:
+    var ac, A2, C2; // helpers for angle and halfaxis-extraction.
+    rh = a_rh;
+    rv = a_rv;
+
+    a_offsetrot = a_offsetrot * (Math.PI / 180); // deg->rad
+    rot = a_offsetrot;
+
+    s = parseFloat(Math.sin(rot));
+    c = parseFloat(Math.cos(rot));
+
+    // build ellipse representation matrix (unit circle transformation).
+    // the 2x2 matrix multiplication with the upper 2x2 of a_mat is inlined.
+    m[0] = matrix.a * +rh * c + matrix.c * rh * s;
+    m[1] = matrix.b * +rh * c + matrix.d * rh * s;
+    m[2] = matrix.a * -rv * s + matrix.c * rv * c;
+    m[3] = matrix.b * -rv * s + matrix.d * rv * c;
+
+    // to implict equation (centered)
+    A = (m[0] * m[0]) + (m[2] * m[2]);
+    C = (m[1] * m[1]) + (m[3] * m[3]);
+    B = (m[0] * m[1] + m[2] * m[3]) * 2.0;
+
+    // precalculate distance A to C
+    ac = A - C;
+
+    // convert implicit equation to angle and halfaxis:
+    if (NEARZERO(B))
+    {
+      a_offsetrot = 0;
+      A2 = A;
+      C2 = C;
+    }
+    else
+    {
+      if (NEARZERO(ac))
+      {
+        A2 = A + B * 0.5;
+        C2 = A - B * 0.5;
+        a_offsetrot = Math.PI / 4.0;
+      }
+      else
+      {
+        // Precalculate radical:
+        var K = 1 + B * B / (ac * ac);
+
+        // Clamp (precision issues might need this.. not likely, but better save than sorry)
+        if (K < 0) K = 0;
+        else K = Math.sqrt(K);
+
+        A2 = 0.5 * (A + C + K * ac);
+        C2 = 0.5 * (A + C - K * ac);
+        a_offsetrot = 0.5 * Math.atan2(B, ac);
+      }
+    }
+
+    // This can get slightly below zero due to rounding issues.
+    // it's save to clamp to zero in this case (this yields a zero length halfaxis)
+    if (A2 < 0) A2 = 0;
+    else A2 = Math.sqrt(A2);
+    if (C2 < 0) C2 = 0;
+    else C2 = Math.sqrt(C2);
+
+    // now A2 and C2 are half-axis:
+    if (ac <= 0)
+    {
+      a_rv = A2;
+      a_rh = C2;
+    }
+    else
+    {
+      a_rv = C2;
+      a_rh = A2;
+    }
+
+    // If the transformation matrix contain a mirror-component
+    // winding order of the ellise needs to be changed.
+    if ((matrix.a * matrix.d) - (matrix.b * matrix.c) < 0)
+    {
+      if (!sweep_flag) sweep_flag = 1;
+      else sweep_flag = 0;
+    }
+
+    // Finally, transform arc endpoint. This takes care about the
+    // translational part which we ignored at the whole math-showdown above.
+    endpoint = endpoint.matrixTransform(matrix);
+
+    // Radians back to degrees
+    a_offsetrot = a_offsetrot * 180 / Math.PI;
+
+    var r = ['A', a_rh, a_rv, a_offsetrot, large_arc_flag, sweep_flag, endpoint.x, endpoint.y];
+    return r;
+  }
+
+  // Parts of Raphaël 2.1.0 (MIT licence: http://raphaeljs.com/license.html)
+  // Contains eg. bugfixed path2curve() function
+
+  var R = {};
+  var has = 'hasOwnProperty';
+  var Str = String;
+  var array = 'array';
+  var isnan = {
+    'NaN': 1,
+    'Infinity': 1,
+    '-Infinity': 1
+  };
+  var lowerCase = Str.prototype.toLowerCase;
+  var upperCase = Str.prototype.toUpperCase;
+  var objectToString = Object.prototype.toString;
+  var concat = 'concat';
+  var split = 'split';
+  var apply = 'apply';
+  var math = Math,
+    mmax = math.max,
+    mmin = math.min,
+    abs = math.abs,
+    pow = math.pow,
+    PI = math.PI,
+    round = math.round,
+    toFloat = parseFloat,
+    toInt = parseInt;
+  var p2s = /,?([achlmqrstvxz]),?/gi;
+  var pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig;
+  var pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig;
+  R.is = function (o, type)
+  {
+    type = lowerCase.call(type);
+    if (type == 'finite')
+    {
+      return !isnan[has](+o);
+    }
+    if (type == 'array')
+    {
+      return o instanceof Array;
+    }
+    return type == 'null' && o === null || type == typeof o && o !== null || type == 'object' && o === Object(o) || type == 'array' && Array.isArray && Array.isArray(o) || objectToString.call(o).slice(8, -1).toLowerCase() == type
+  };
+
+  function clone(obj)
+  {
+    if (Object(obj) !== obj)
+    {
+      return obj;
+    }
+    var res = new obj.constructor;
+    for (var key in obj)
+    {
+      if (obj[has](key))
+      {
+        res[key] = clone(obj[key]);
+      }
+    }
+    return res;
+  }
+  R._path2string = function ()
+  {
+    return this.join(',').replace(p2s, '$1');
+  };
+
+  var pathClone = function (pathArray)
+  {
+    var res = clone(pathArray);
+    res.toString = R._path2string;
+    return res;
+  };
+  var paths = function (ps)
+  {
+    var p = paths.ps = paths.ps ||
+    {};
+    if (p[ps]) p[ps].sleep = 100;
+    else p[ps] = {
+      sleep: 100
+    };
+    setTimeout(function ()
+    {
+      for (var key in p)
+      {
+        if (p[has](key) && key != ps)
+        {
+          p[key].sleep--;
+          !p[key].sleep && delete p[key];
+        }
+      }
+    });
+    return p[ps];
+  };
+
+  function catmullRom2bezier(crp, z)
+  {
+    var d = [];
+    for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2)
+    {
+      var p = [
+        {
+          x: +crp[i - 2],
+          y: +crp[i - 1]
+        },
+        {
+          x: +crp[i],
+          y: +crp[i + 1]
+        },
+        {
+          x: +crp[i + 2],
+          y: +crp[i + 3]
+        },
+        {
+          x: +crp[i + 4],
+          y: +crp[i + 5]
+        }];
+      if (z)
+      {
+        if (!i)
+        {
+          p[0] = {
+            x: +crp[iLen - 2],
+            y: +crp[iLen - 1]
+          };
+        }
+        else
+        {
+          if (iLen - 4 == i)
+          {
+            p[3] = {
+              x: +crp[0],
+              y: +crp[1]
+            };
+          }
+          else
+          {
+            if (iLen - 2 == i)
+            {
+              p[2] = {
+                x: +crp[0],
+                y: +crp[1]
+              };
+              p[3] = {
+                x: +crp[2],
+                y: +crp[3]
+              };
+            }
+          }
+        }
+      }
+      else
+      {
+        if (iLen - 4 == i)
+        {
+          p[3] = p[2];
+        }
+        else
+        {
+          if (!i)
+          {
+            p[0] = {
+              x: +crp[i],
+              y: +crp[i + 1]
+            };
+          }
+        }
+      }
+      d.push(['C', (-p[0].x + 6 * p[1].x + p[2].x) / 6, (-p[0].y + 6 * p[1].y + p[2].y) / 6, (p[1].x + 6 * p[2].x - p[3].x) / 6, (p[1].y + 6 * p[2].y - p[3].y) / 6, p[2].x, p[2].y])
+    }
+    return d
+  };
+  var parsePathString = function (pathString)
+  {
+    if (!pathString) return null;
+    var pth = paths(pathString);
+    if (pth.arr) return pathClone(pth.arr)
+    var paramCounts = {
+      a: 7,
+      c: 6,
+      h: 1,
+      l: 2,
+      m: 2,
+      r: 4,
+      q: 4,
+      s: 4,
+      t: 2,
+      v: 1,
+      z: 0
+    }, data = [];
+    if (R.is(pathString, array) && R.is(pathString[0], array)) data = pathClone(pathString);
+    if (!data.length)
+    {
+      Str(pathString).replace(pathCommand, function (a, b, c)
+      {
+        var params = [],
+          name = b.toLowerCase();
+        c.replace(pathValues, function (a, b)
+        {
+          b && params.push(+b);
+        });
+        if (name == 'm' && params.length > 2)
+        {
+          data.push([b][concat](params.splice(0, 2)));
+          name = 'l';
+          b = b == 'm' ? 'l' : 'L'
+        }
+        if (name == 'r') data.push([b][concat](params))
+        else
+        {
+          while (params.length >= paramCounts[name])
+          {
+            data.push([b][concat](params.splice(0, paramCounts[name])));
+            if (!paramCounts[name]) break;
+          }
+        }
+      })
+    }
+    data.toString = R._path2string;
+    pth.arr = pathClone(data);
+    return data;
+  };
+
+  function repush(array, item)
+  {
+    for (var i = 0, ii = array.length; i < ii; i++)
+      if (array[i] === item)
+      {
+        return array.push(array.splice(i, 1)[0]);
+      }
+  }
+
+  var pathToAbsolute = cacher(function (pathArray)
+  {
+    //var pth = paths(pathArray); // Timo: commented to prevent multiple caching
+    // for some reason only FF proceed correctly
+    // when not cached using cacher() around
+    // this function.
+    //if (pth.abs) return pathClone(pth.abs)
+    if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array))
+      pathArray = parsePathString(pathArray)
+    if (!pathArray || !pathArray.length) return [['M', 0, 0]];
+    var res = [],
+      x = 0,
+      y = 0,
+      mx = 0,
+      my = 0,
+      start = 0;
+    if (pathArray[0][0] == 'M')
+    {
+      x = +pathArray[0][1];
+      y = +pathArray[0][2];
+      mx = x;
+      my = y;
+      start++;
+      res[0] = ['M', x, y];
+    }
+    var crz = pathArray.length == 3 && pathArray[0][0] == 'M' && pathArray[1][0].toUpperCase() == 'R' && pathArray[2][0].toUpperCase() == 'Z';
+    for (var r, pa, i = start, ii = pathArray.length; i < ii; i++)
+    {
+      res.push(r = []);
+      pa = pathArray[i];
+      if (pa[0] != upperCase.call(pa[0]))
+      {
+        r[0] = upperCase.call(pa[0]);
+        switch (r[0])
+        {
+        case 'A':
+          r[1] = pa[1];
+          r[2] = pa[2];
+          r[3] = pa[3];
+          r[4] = pa[4];
+          r[5] = pa[5];
+          r[6] = +(pa[6] + x);
+          r[7] = +(pa[7] + y);
+          break;
+        case 'V':
+          r[1] = +pa[1] + y;
+          break;
+        case 'H':
+          r[1] = +pa[1] + x;
+          break;
+        case 'R':
+          var dots = [x, y][concat](pa.slice(1));
+          for (var j = 2, jj = dots.length; j < jj; j++)
+          {
+            dots[j] = +dots[j] + x;
+            dots[++j] = +dots[j] + y
+          }
+          res.pop();
+          res = res[concat](catmullRom2bezier(dots, crz));
+          break;
+        case 'M':
+          mx = +pa[1] + x;
+          my = +pa[2] + y;
+        default:
+          for (j = 1, jj = pa.length; j < jj; j++)
+            r[j] = +pa[j] + (j % 2 ? x : y)
+        }
+      }
+      else
+      {
+        if (pa[0] == 'R')
+        {
+          dots = [x, y][concat](pa.slice(1));
+          res.pop();
+          res = res[concat](catmullRom2bezier(dots, crz));
+          r = ['R'][concat](pa.slice(-2));
+        }
+        else
+        {
+          for (var k = 0, kk = pa.length; k < kk; k++)
+            r[k] = pa[k]
+        }
+      }
+      switch (r[0])
+      {
+      case 'Z':
+        x = mx;
+        y = my;
+        break;
+      case 'H':
+        x = r[1];
+        break;
+      case 'V':
+        y = r[1];
+        break;
+      case 'M':
+        mx = r[r.length - 2];
+        my = r[r.length - 1];
+      default:
+        x = r[r.length - 2];
+        y = r[r.length - 1];
+      }
+    }
+    res.toString = R._path2string;
+    //pth.abs = pathClone(res);
+    return res;
+  });
+
+  function cacher(f, scope, postprocessor)
+  {
+    function newf()
+    {
+      var arg = Array.prototype.slice.call(arguments, 0),
+        args = arg.join('\u2400'),
+        cache = newf.cache = newf.cache ||
+        {}, count = newf.count = newf.count || [];
+      if (cache.hasOwnProperty(args))
+      {
+        for (var i = 0, ii = count.length; i < ii; i++)
+          if (count[i] === args)
+          {
+            count.push(count.splice(i, 1)[0]);
+          }
+        return postprocessor ? postprocessor(cache[args]) : cache[args];
+      }
+      count.length >= 1E3 && delete cache[count.shift()];
+      count.push(args);
+      cache[args] = f.apply(scope, arg);
+      return postprocessor ? postprocessor(cache[args]) : cache[args];
+    }
+    return newf;
+  }
+
+  var l2c = function (x1, y1, x2, y2)
+  {
+    return [x1, y1, x2, y2, x2, y2];
+  },
+    q2c = function (x1, y1, ax, ay, x2, y2)
+    {
+      var _13 = 1 / 3,
+        _23 = 2 / 3;
+      return [_13 * x1 + _23 * ax, _13 * y1 + _23 * ay, _13 * x2 + _23 * ax, _13 * y2 + _23 * ay, x2, y2]
+    },
+    a2c = cacher(function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive)
+    {
+      var _120 = PI * 120 / 180,
+        rad = PI / 180 * (+angle || 0),
+        res = [],
+        xy,
+        rotate = cacher(function (x, y, rad)
+        {
+          var X = x * Math.cos(rad) - y * Math.sin(rad),
+            Y = x * Math.sin(rad) + y * Math.cos(rad);
+          return {
+            x: X,
+            y: Y
+          };
+        });
+      if (!recursive)
+      {
+        xy = rotate(x1, y1, -rad);
+        x1 = xy.x;
+        y1 = xy.y;
+        xy = rotate(x2, y2, -rad);
+        x2 = xy.x;
+        y2 = xy.y;
+        var cos = Math.cos(PI / 180 * angle),
+          sin = Math.sin(PI / 180 * angle),
+          x = (x1 - x2) / 2,
+          y = (y1 - y2) / 2;
+        var h = x * x / (rx * rx) + y * y / (ry * ry);
+        if (h > 1)
+        {
+          h = Math.sqrt(h);
+          rx = h * rx;
+          ry = h * ry;
+        }
+        var rx2 = rx * rx,
+          ry2 = ry * ry,
+          k = (large_arc_flag == sweep_flag ? -1 : 1) * Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+          cx = k * rx * y / ry + (x1 + x2) / 2,
+          cy = k * -ry * x / rx + (y1 + y2) / 2,
+          f1 = Math.asin(((y1 - cy) / ry).toFixed(9)),
+          f2 = Math.asin(((y2 - cy) / ry).toFixed(9));
+        f1 = x1 < cx ? PI - f1 : f1;
+        f2 = x2 < cx ? PI - f2 : f2;
+        f1 < 0 && (f1 = PI * 2 + f1);
+        f2 < 0 && (f2 = PI * 2 + f2);
+        if (sweep_flag && f1 > f2)
+        {
+          f1 = f1 - PI * 2;
+        }
+        if (!sweep_flag && f2 > f1)
+        {
+          f2 = f2 - PI * 2;
+        }
+      }
+      else
+      {
+        f1 = recursive[0];
+        f2 = recursive[1];
+        cx = recursive[2];
+        cy = recursive[3];
+      }
+      var df = f2 - f1;
+      if (Math.abs(df) > _120)
+      {
+        var f2old = f2,
+          x2old = x2,
+          y2old = y2;
+        f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+        x2 = cx + rx * Math.cos(f2);
+        y2 = cy + ry * Math.sin(f2);
+        res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy])
+      }
+      df = f2 - f1;
+      var c1 = Math.cos(f1),
+        s1 = Math.sin(f1),
+        c2 = Math.cos(f2),
+        s2 = Math.sin(f2),
+        t = Math.tan(df / 4),
+        hx = 4 / 3 * rx * t,
+        hy = 4 / 3 * ry * t,
+        m1 = [x1, y1],
+        m2 = [x1 + hx * s1, y1 - hy * c1],
+        m3 = [x2 + hx * s2, y2 - hy * c2],
+        m4 = [x2, y2];
+      m2[0] = 2 * m1[0] - m2[0];
+      m2[1] = 2 * m1[1] - m2[1];
+      if (recursive) return [m2, m3, m4].concat(res);
+      else
+      {
+        res = [m2, m3, m4].concat(res).join().split(',');
+        var newres = [];
+        for (var i = 0, ii = res.length; i < ii; i++)
+          newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x
+        return newres
+      }
+    });
+
+  var path2curve = cacher(function (path, path2)
+  {
+    var pth = !path2 && paths(path);
+    if (!path2 && pth.curve) return pathClone(pth.curve)
+    var p = pathToAbsolute(path),
+      p2 = path2 && pathToAbsolute(path2),
+      attrs = {
+        x: 0,
+        y: 0,
+        bx: 0,
+        by: 0,
+        X: 0,
+        Y: 0,
+        qx: null,
+        qy: null
+      },
+      attrs2 = {
+        x: 0,
+        y: 0,
+        bx: 0,
+        by: 0,
+        X: 0,
+        Y: 0,
+        qx: null,
+        qy: null
+      },
+      processPath = function (path, d, pcom)
+      {
+        var nx, ny;
+        if (!path)
+        {
+          return ['C', d.x, d.y, d.x, d.y, d.x, d.y];
+        }!(path[0] in
+        {
+          T: 1,
+          Q: 1
+        }) && (d.qx = d.qy = null);
+        switch (path[0])
+        {
+        case 'M':
+          d.X = path[1];
+          d.Y = path[2];
+          break;
+        case 'A':
+          path = ['C'][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
+          break;
+        case 'S':
+          if (pcom == 'C' || pcom == 'S')
+          {
+            nx = d.x * 2 - d.bx;
+            ny = d.y * 2 - d.by;
+          }
+          else
+          {
+            nx = d.x;
+            ny = d.y;
+          }
+          path = ['C', nx, ny][concat](path.slice(1));
+          break;
+        case 'T':
+          if (pcom == 'Q' || pcom == 'T')
+          {
+            d.qx = d.x * 2 - d.qx;
+            d.qy = d.y * 2 - d.qy;
+          }
+          else
+          {
+            d.qx = d.x;
+            d.qy = d.y;
+          }
+          path = ['C'][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+          break;
+        case 'Q':
+          d.qx = path[1];
+          d.qy = path[2];
+          path = ['C'][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+          break;
+        case 'L':
+          path = ['C'][concat](l2c(d.x, d.y, path[1], path[2]));
+          break;
+        case 'H':
+          path = ['C'][concat](l2c(d.x, d.y, path[1], d.y));
+          break;
+        case 'V':
+          path = ['C'][concat](l2c(d.x, d.y, d.x, path[1]));
+          break;
+        case 'Z':
+          path = ['C'][concat](l2c(d.x, d.y, d.X, d.Y));
+          break
+        }
+        return path
+      },
+      fixArc = function (pp, i)
+      {
+        if (pp[i].length > 7)
+        {
+          pp[i].shift();
+          var pi = pp[i];
+          while (pi.length)
+          {
+            pcoms1[i] = 'A';
+            p2 && (pcoms2[i] = 'A');
+            pp.splice(i++, 0, ['C'][concat](pi.splice(0, 6)));
+          }
+          pp.splice(i, 1);
+          ii = mmax(p.length, p2 && p2.length || 0);
+        }
+      },
+      fixM = function (path1, path2, a1, a2, i)
+      {
+        if (path1 && path2 && path1[i][0] == 'M' && path2[i][0] != 'M')
+        {
+          path2.splice(i, 0, ['M', a2.x, a2.y]);
+          a1.bx = 0;
+          a1.by = 0;
+          a1.x = path1[i][1];
+          a1.y = path1[i][2];
+          ii = mmax(p.length, p2 && p2.length || 0);
+        }
+      },
+      pcoms1 = [],
+      pcoms2 = [],
+      pfirst = '',
+      pcom = '';
+    for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++)
+    {
+      p[i] && (pfirst = p[i][0]);
+      if (pfirst != 'C')
+      {
+        pcoms1[i] = pfirst;
+        i && (pcom = pcoms1[i - 1]);
+      }
+      p[i] = processPath(p[i], attrs, pcom);
+      if (pcoms1[i] != 'A' && pfirst == 'C') pcoms1[i] = 'C';
+      fixArc(p, i);
+      if (p2)
+      {
+        p2[i] && (pfirst = p2[i][0]);
+        if (pfirst != 'C')
+        {
+          pcoms2[i] = pfirst;
+          i && (pcom = pcoms2[i - 1]);
+        }
+        p2[i] = processPath(p2[i], attrs2, pcom);
+        if (pcoms2[i] != 'A' && pfirst == 'C') pcoms2[i] = 'C'
+        fixArc(p2, i);
+      }
+      fixM(p, p2, attrs, attrs2, i);
+      fixM(p2, p, attrs2, attrs, i);
+      var seg = p[i],
+        seg2 = p2 && p2[i],
+        seglen = seg.length,
+        seg2len = p2 && seg2.length;
+      attrs.x = seg[seglen - 2];
+      attrs.y = seg[seglen - 1];
+      attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+      attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+      attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+      attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+      attrs2.x = p2 && seg2[seg2len - 2];
+      attrs2.y = p2 && seg2[seg2len - 1];
+    }
+    if (!p2) pth.curve = pathClone(p);
+    return p2 ? [p, p2] : p
+  }, null, pathClone);
diff --git a/src/i18n.js b/src/i18n.js
index 8848502..f2c773c 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -33,6 +33,9 @@ export default {
             absoluteMode: 'ABSOLUTE',
             percentMode: 'PERCENT',
 
+            builtin: 'Builtin',
+            upload: 'Upload SVG or PNG',
+
             setOptionError: 'Something Unexpected Happerns. Click refresh to try again!',
 
             refresh: 'Refresh',
@@ -72,6 +75,9 @@ export default {
             absoluteMode: '绝对值',
             percentMode: '百分比',
 
+            builtin: '内置',
+            upload: '上传 SVG 或 PNG',
+
             setOptionError: '发生了一些意料之外的错误,点击刷新再试试!',
 
             refresh: '刷新',
diff --git a/tool/md2json.js b/tool/md2json.js
index 34f508b..3edb06f 100644
--- a/tool/md2json.js
+++ b/tool/md2json.js
@@ -224,7 +224,8 @@ function mdToJsonSchema(mdStr, maxDepth, imagePath) {
                 }
                 else if (tagName.startsWith('exampleuicontrol')) {
                     const type = tagName.replace('exampleuicontrol', '').toLowerCase();
-                    if (['boolean', 'color', 'number', 'vector', 'enum', 'angle', 'percent', 'percentvector', 'text']
+                    if (['boolean', 'color', 'number', 'vector', 'enum', 'angle', 'percent', 'percentvector', 'text',
+                        'icon']
                         .indexOf(type) < 0) {
                         console.error(`Unkown ExampleUIControl Type ${type}`);
                     }
@@ -246,7 +247,7 @@ function mdToJsonSchema(mdStr, maxDepth, imagePath) {
                 if (currentExampleCode) {
                     // Get code from map;
                     if (!codeMap[data]) {
-                        throw new Error('Can\'t find code.', codeMap, data);
+                        console.error('Can\'t find code.', codeMap, data);
                     }
                     currentExampleCode.code = codeMap[data];
                 }
diff --git a/zh/option/component/angle-axis.md b/zh/option/component/angle-axis.md
index 119daf5..6609043 100644
--- a/zh/option/component/angle-axis.md
+++ b/zh/option/component/angle-axis.md
@@ -5,6 +5,73 @@
 
 极坐标系的角度轴。
 
+<ExampleBaseOption name="two-number-axis" title="双数值轴">
+const data = [];
+
+for (let i = 0; i <= 360; i++) {
+    const t = i / 180 * Math.PI;
+    const r = Math.sin(2 * t) * Math.cos(2 * t);
+    data.push([r, i]);
+}
+
+const option = {
+    polar: {
+        center: ['50%', '54%']
+    },
+    tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+            type: 'cross'
+        }
+    },
+    angleAxis: {
+        type: 'value',
+        startAngle: 0,
+    },
+    radiusAxis: {
+        min: 0
+    },
+    series: [{
+        coordinateSystem: 'polar',
+        name: 'line',
+        type: 'line',
+        showSymbol: false,
+        data: data
+    }],
+    animationDuration: 2000
+};
+</ExampleBaseOption>
+
+<ExampleBaseOption name="two-category-axis" title="双类目轴">
+
+const hours = ['12a', '1a', '2a', '3a', '4a', '5a', '6a',
+        '7a', '8a', '9a','10a','11a',
+        '12p', '1p', '2p', '3p', '4p', '5p',
+        '6p', '7p', '8p', '9p', '10p', '11p'];
+const days = ['Saturday', 'Friday', 'Thursday',
+        'Wednesday', 'Tuesday', 'Monday', 'Sunday'];
+
+const option = {
+    title: {
+        text: 'Punch Card of Github'
+    },
+    legend: {
+        data: ['Punch Card'],
+        left: 'right'
+    },
+    polar: {},
+    angleAxis: {
+        type: 'category',
+        data: hours,
+        boundaryGap: false
+    },
+    radiusAxis: {
+        type: 'category',
+        data: days
+    }
+};
+</ExampleBaseOption>
+
 {{use: partial-component-id(prefix="#")}}
 
 ## polarIndex(number) = 0
@@ -13,6 +80,8 @@
 
 ## startAngle(number) = 90
 
+<ExampleUIControlAngle default="90" min="-360" max="360" step="1" />
+
 起始刻度的角度,默认为 90 度,即圆心的正上方。0 度为圆心的正右方。
 
 如下示例是 startAngle 为 45 的效果:
@@ -21,6 +90,8 @@
 
 ## clockwise(boolean) = true
 
+<ExampleUIControlBoolean default="true" />
+
 刻度增长是否按顺时针,默认顺时针。
 
 如下示例是 clockwise 为 `false` (逆时针)的效果:
diff --git a/zh/option/component/axis-common.md b/zh/option/component/axis-common.md
index 87ad2e4..a6174b1 100644
--- a/zh/option/component/axis-common.md
+++ b/zh/option/component/axis-common.md
@@ -42,7 +42,7 @@ X 轴或者 Y 轴的轴线是否在另一个轴的 0 刻度上,只有在另一
 
 ##${prefix} symbol(string|Array) = 'none'
 
-<ExampleUIControlEnum default="none" options="none,circle,rect,roundRect,triangle,diamond,pin,arrow" />
+<ExampleUIControlIcon default="none" />
 
 轴线两边的箭头。可以是字符串,表示两端使用同样的箭头;或者长度为 2 的字符串数组,分别表示两端的箭头。默认不显示箭头,即 `'none'`。两端都显示箭头可以设置为 `'arrow'`,只在末端显示箭头可以设置为 `['none', 'arrow']`。
 
diff --git a/zh/option/component/data-zoom-inside.md b/zh/option/component/data-zoom-inside.md
index adffbb0..de904dc 100644
--- a/zh/option/component/data-zoom-inside.md
+++ b/zh/option/component/data-zoom-inside.md
@@ -14,8 +14,42 @@
     + 移动端:在移动端触屏上,支持两指滑动缩放。
 
 
-<br>
-<br>
+<ExampleBaseOption name="data-zoom-inside" title="使用拖拽滚轮平移缩放">
+const data = [["2014-07-14",156],["2014-07-15",140],["2014-07-16",133],["2014-07-17",186],["2014-07-18",182],["2014-07-19",106],["2014-07-20",119],["2014-07-21",68],["2014-07-22",54],["2014-07-23",82],["2014-07-24",90],["2014-07-25",134],["2014-07-26",188],["2014-07-27",194],["2014-07-28",159],["2014-07-29",159],["2014-07-30",169],["2014-07-31",244],["2014-08-01",199],["2014-08-02",163],["2014-08-03",149],["2014-08-05",80],["2014-08-06",67],["2014-08-07",162],["2014-08-08",140],["2014-08 [...]
+
+const option = {
+    color: ['#3398DB'],
+    title: {
+        text: 'Beijing AQI'
+    },
+    tooltip: {
+        trigger: 'axis'
+    },
+    xAxis: {
+        data: data.map(function (item) {
+            return item[0];
+        })
+    },
+    yAxis: {
+        splitLine: {
+            show: false
+        }
+    },
+    dataZoom: [{
+        start: 80,
+        type: 'inside'
+    }, {
+        start: 80
+    }],
+    series: {
+        name: 'Beijing AQI',
+        type: 'bar',
+        data: data.map(function (item) {
+            return item[1];
+        })
+    }
+};
+</ExampleBaseOption>
 
 
 ## type(string) = 'inside'
@@ -24,6 +58,8 @@
 
 ## disabled(boolean) = false
 
+<ExampleUIControlBoolean />
+
 是否停止组件的功能。
 
 {{ use: partial-data-zoom-common(
@@ -34,6 +70,8 @@
 
 ## zoomOnMouseWheel(boolean|string) = true
 
+<ExampleUIControlEnum options="true,false,shift,ctrl,alt" default="true" />
+
 如何触发缩放。可选值为:
 
 + `true`:表示不按任何功能键,鼠标滚轮能触发缩放。
@@ -44,6 +82,8 @@
 
 ## moveOnMouseMove(boolean|string) = true
 
+<ExampleUIControlEnum options="true,false,shift,ctrl,alt" default="true" />
+
 如何触发数据窗口平移。可选值为:
 
 + `true`:表示不按任何功能键,鼠标移动能触发数据窗口平移。
@@ -54,6 +94,8 @@
 
 ## moveOnMouseWheel(boolean|string) = true
 
+<ExampleUIControlEnum options="true,false,shift,ctrl,alt" default="true" />
+
 如何触发数据窗口平移。可选值为:
 
 + `true`:表示不按任何功能键,鼠标滚轮能触发数据窗口平移。
@@ -64,4 +106,6 @@
 
 ## preventDefaultMouseMove(boolean) = true
 
+<ExampleUIControlBoolean default="true" />
+
 是否阻止 mousemove 事件的默认行为。
\ No newline at end of file
diff --git a/zh/option/component/data-zoom-slider.md b/zh/option/component/data-zoom-slider.md
index 2c5b2a2..adb65d7 100644
--- a/zh/option/component/data-zoom-slider.md
+++ b/zh/option/component/data-zoom-slider.md
@@ -6,9 +6,36 @@
 
 (参考[数据区域缩放组件(dataZoom)的介绍](~dataZoom))
 
-
-<br>
-<br>
+<ExampleBaseOption name="data-zoom-slider" title="滑块缩放的 dataZoom">
+const data = [["2014-07-14",156],["2014-07-15",140],["2014-07-16",133],["2014-07-17",186],["2014-07-18",182],["2014-07-19",106],["2014-07-20",119],["2014-07-21",68],["2014-07-22",54],["2014-07-23",82],["2014-07-24",90],["2014-07-25",134],["2014-07-26",188],["2014-07-27",194],["2014-07-28",159],["2014-07-29",159],["2014-07-30",169],["2014-07-31",244],["2014-08-01",199],["2014-08-02",163],["2014-08-03",149],["2014-08-05",80],["2014-08-06",67],["2014-08-07",162],["2014-08-08",140],["2014-08 [...]
+
+const option = {
+    color: ['#3398DB'],
+    tooltip: {
+        trigger: 'axis'
+    },
+    xAxis: {
+        data: data.map(function (item) {
+            return item[0];
+        })
+    },
+    yAxis: {
+        splitLine: {
+            show: false
+        }
+    },
+    dataZoom: [{
+        start: 80
+    }],
+    series: {
+        name: 'Beijing AQI',
+        type: 'bar',
+        data: data.map(function (item) {
+            return item[1];
+        })
+    }
+};
+</ExampleBaseOption>
 
 
 ## type(string) = 'slider'
@@ -17,11 +44,15 @@
 
 ## show(boolean) = true
 
+<ExampleUIControlBoolean default="true" />
+
 是否显示 ${dataZoomName} 组件。如果设置为 `false`,不会显示,但是数据过滤的功能还存在。
 
 
 ## backgroundColor(Color) = 'rgba(47,69,84,0)'
 
+<ExampleUIControlColor default="rgba(47,69,84,0)" />
+
 组件的背景颜色。
 
 
@@ -53,10 +84,14 @@
 
 ## fillerColor(Color) = 'rgba(167,183,204,0.4)'
 
+<ExampleUIControlColor default="rgba(167,183,204,0.4)" />
+
 选中范围的填充颜色。
 
 ## borderColor(Color) = '#ddd'
 
+<ExampleUIControlColor default="#ddd" />
+
 边框颜色。
 
 
@@ -73,6 +108,8 @@
 
 ## handleSize(number|string) = '100%'
 
+<ExampleUIControlPercent min="0" step="1" default="100%" />
+
 控制手柄的尺寸,可以是像素大小,也可以是相对于 dataZoom 组件宽度的百分比,默认跟 dataZoom 宽度相同。
 
 ## handleStyle(Object)
@@ -87,6 +124,8 @@
 
 ## labelPrecision(number|string) = 'auto'
 
+<ExampleUIControlNumber min="0" step="1" />
+
 显示label的小数精度。默认根据数据自动决定。
 
 
@@ -113,16 +152,22 @@ labelFormatter: function (value) {
 
 ## showDetail(boolean) = true
 
+<ExampleUIControlBoolean default="true" />
+
 是否显示detail,即拖拽时候显示详细数值信息。
 
 
 ## showDataShadow(string) = 'auto'
 
+<ExampleUIControlBoolean />
+
 是否在 `dataZoom-silder` 组件中显示数据阴影。数据阴影可以简单地反应数据走势。
 
 
 ## realtime(boolean) = true
 
+<ExampleUIControlBoolean default="true" />
+
 拖动时,是否实时更新系列的视图。如果设置为 `false`,则只在拖拽结束的时候更新。
 
 
diff --git a/zh/option/component/data-zoom.md b/zh/option/component/data-zoom.md
index 64f3ad5..5659c17 100644
--- a/zh/option/component/data-zoom.md
+++ b/zh/option/component/data-zoom.md
@@ -114,11 +114,6 @@ option = {
         如果需要改变这种处理顺序,那么改变 `dataZoomX` 和 `dataZoomY` 在 option 中的出现顺序即可。
 
 
-<br>
-<br>
-
-
-
 {{import: component-data-zoom-inside}}
 {{import: component-data-zoom-slider}}
 
@@ -200,11 +195,15 @@ option: {
 
 ## filterMode(string) = 'filter'
 
+<ExampleUIControlEnum options="filter,weakFilter,empty,none" default="filter" />
+
 {{use: partial-data-zoom-filterMode}}
 
 
 ## start(number) = 0
 
+<ExampleUIControlNumber min="0" max="100" step="0.5" />
+
 数据窗口范围的起始百分比。范围是:0 ~ 100。表示 0% ~ 100%。
 
 [${dataZoomName}.start](~${dataZoomName}.start) 和 [${dataZoomName}.end](~${dataZoomName}.end) 共同用 **百分比** 的形式定义了数据窗口范围。
@@ -214,6 +213,8 @@ option: {
 
 ## end(number) = 100
 
+<ExampleUIControlNumber min="0" max="100" default="100" step="0.5" />
+
 数据窗口范围的结束百分比。范围是:0 ~ 100。
 
 [${dataZoomName}.start](~${dataZoomName}.start) 和 [${dataZoomName}.end](~${dataZoomName}.end) 共同用 **百分比** 的形式定义了数据窗口范围。
@@ -247,12 +248,16 @@ option: {
 
 ## minSpan(number) = null
 
+<ExampleUIControlNumber min="0" max="100" step="0.5" />
+
 用于限制窗口大小的最小值(百分比值),取值范围是 `0` ~ `100`。
 
 如果设置了 [${dataZoomName}.minValueSpan](~${dataZoomName}.minValueSpan) 则 `minSpan` 失效。
 
 ## maxSpan(number) = null
 
+<ExampleUIControlNumber min="0" max="100" step="0.5" />
+
 用于限制窗口大小的最大值(百分比值),取值范围是 `0` ~ `100`。
 
 如果设置了 [${dataZoomName}.maxValueSpan](~${dataZoomName}.maxValueSpan) 则 `maxSpan` 失效。
@@ -273,6 +278,8 @@ option: {
 
 ## orient(string) = null
 
+<ExampleUIControlEnum options="horizontal,vertical" />
+
 布局方式是横还是竖。不仅是布局方式,对于直角坐标系而言,也决定了,缺省情况控制横向数轴还是纵向数轴。
 
 可选值为:
@@ -284,6 +291,8 @@ option: {
 
 ## zoomLock(boolean) = false
 
+<ExampleUIControlBoolean />
+
 是否锁定选择区域(或叫做数据窗口)的大小。
 
 如果设置为 `true` 则锁定选择区域的大小,也就是说,只能平移,不能缩放。
@@ -291,6 +300,8 @@ option: {
 
 ## throttle(number) = 100
 
+<ExampleUIControlNumber default="100" min="0" step="20" />
+
 设置触发视图刷新的频率。单位为毫秒(ms)。
 
 如果 [animation](~animation) 设为 `true` 且 [animationDurationUpdate](~animationDurationUpdate) 大于 `0`,可以保持 `throttle` 为默认值 `100`(或者设置为大于 `0` 的值),否则拖拽时有可能画面感觉卡顿。
diff --git a/zh/option/component/legend.md b/zh/option/component/legend.md
index ac15747..71c814c 100644
--- a/zh/option/component/legend.md
+++ b/zh/option/component/legend.md
@@ -175,6 +175,8 @@ const option = {
 
 ## symbolKeepAspect(boolean) = true
 
+<ExampleUIControlBoolean />
+
 如果图标(可能来自系列的 `symbol` 或用户自定义的 `legend.data.icon`)是 `path://` 的形式,是否在缩放时保持该图形的长宽比。
 
 ## formatter(string|Function) = null
@@ -248,6 +250,8 @@ legend: {
 
 ## icon(string)
 
+<ExampleUIControlIcon />
+
 图例项的 icon。
 
 {{ use: partial-icon }}
diff --git a/zh/option/component/radar.md b/zh/option/component/radar.md
index 1952691..8a9d088 100644
--- a/zh/option/component/radar.md
+++ b/zh/option/component/radar.md
@@ -11,6 +11,42 @@
 
 ~[400x400](${galleryViewPath}doc-example/radar&edit=1&reset=1)
 
+<ExampleBaseOption name="radar" title="基础雷达图">
+const option = {
+    title: {
+        text: '基础雷达图'
+    },
+    tooltip: {},
+    legend: {
+        data: ['Allocated Budget', 'Actual Spending']
+    },
+    radar: {
+        indicator: [
+            { name: 'sales', max: 6500},
+            { name: 'Administration', max: 16000},
+            { name: 'Information Techology', max: 30000},
+            { name: 'Customer Support', max: 38000},
+            { name: 'Development', max: 52000},
+            { name: 'Marketing', max: 25000}
+        ]
+    },
+    series: [{
+        name: '预算 vs 开销(Budget vs spending)',
+        type: 'radar',
+        data: [
+            {
+                value: [4300, 10000, 28000, 35000, 50000, 19000],
+                name: 'Allocated Budget'
+            },
+            {
+                value: [5000, 14000, 28000, 31000, 42000, 21000],
+                name: 'Actual Spending'
+            }
+        ]
+    }]
+};
+</ExampleBaseOption>
+
 {{use: partial-component-id(prefix="#")}}
 
 {{use: component-circular-layout(
@@ -51,22 +87,26 @@ formatter: function (value, indicator) {
 
 ## nameGap(number) = 15
 
-指示器名称和指示器轴的距离。
+<ExampleUIControlNumber default="15" step="1" />
 
-## axisType(string) = 'value'
-{{ use: partial-version(version = "4.5.0") }}
-{{ use: partial-axis-type-content() }}
+指示器名称和指示器轴的距离。
 
 ## splitNumber(number) = 5
 
+<ExampleUIControlNumber default="15" step="1" />
+
 指示器轴的分割段数。
 
 ## shape(string) = 'polygon'
 
+<ExampleUIControlEnum options="polygon,circle" />
+
 雷达图绘制类型,支持 `'polygon'` 和 `'circle'`。
 
 ## scale(boolean) = false
 
+<ExampleUIControlBoolean />
+
 是否是脱离 0 值比例。设置成 `true` 后坐标刻度不会强制包含零刻度。在双数值轴的散点图中比较有用。
 
 {{ use: partial-axis-common-axis-line(
@@ -118,12 +158,18 @@ indicator: [
 
 ### max(number)
 
+<ExampleUIControlNumber default="100" step="1" />
+
 指示器的最大值,可选,建议设置
 
 ### min(number)
 
+<ExampleUIControlNumber step="1" />
+
 指示器的最小值,可选,默认为 0。
 
 ### color(string)
 
+<ExampleUIControlColor />
+
 标签特定的颜色。
\ No newline at end of file
diff --git a/zh/option/partial/symbol.md b/zh/option/partial/symbol.md
index ebe6a8d..044bdab 100644
--- a/zh/option/partial/symbol.md
+++ b/zh/option/partial/symbol.md
@@ -2,7 +2,7 @@
 
 #${prefix} symbol(string{{ if: ${hasCallback} }}|Function{{ /if}}) = ${defaultSymbol}
 
-<ExampleUIControlEnum default="circle" options="circle,rect,roundRect,triangle,diamond,pin,arrow,none" />
+<ExampleUIControlIcon default="circle" />
 
 ${name}标记的图形。
 
@@ -48,6 +48,8 @@ ${name}标记的旋转角度(而非弧度)。正值表示逆时针旋转。
 
 #${prefix} symbolKeepAspect(boolean) = false
 
+<ExampleUIControlBoolean />
+
 如果 `symbol` 是 `path://` 的形式,是否在缩放时保持该图形的长宽比。
 
 #${prefix} symbolOffset(Array) = [0, 0]
diff --git a/zh/option/series/pie.md b/zh/option/series/pie.md
index aa9b486..405d0b6 100644
--- a/zh/option/series/pie.md
+++ b/zh/option/series/pie.md
@@ -39,30 +39,9 @@ const option = {
     ]
 };
 
-<ExampleBaseOption name="pie" title="很多数据的饼图">
-const option = {
-    legend: {
-        orient: 'vertical',
-        left: 'left',
-        data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
-    },
-    series: [
-        {
-            name: '访问来源',
-            type: 'pie',
-            data: [
-                {value: 335, name: '直接访问'},
-                {value: 310, name: '邮件营销'},
-                {value: 234, name: '联盟广告'},
-                {value: 135, name: '视频广告'},
-                {value: 1548, name: '搜索引擎'}
-            ]
-        }
-    ]
-};
-
 </ExampleBaseOption>
 
+
 ## type(string) = 'pie'
 
 {{use: partial-component-id(prefix="#")}}
diff --git a/zh/option/series/radar.md b/zh/option/series/radar.md
index 20daa2d..5b5ba28 100644
--- a/zh/option/series/radar.md
+++ b/zh/option/series/radar.md
@@ -18,16 +18,16 @@ const option = {
     },
     tooltip: {},
     legend: {
-        data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)']
+        data: ['Allocated Budget', 'Actual Spending']
     },
     radar: {
         indicator: [
-            { name: '销售(sales)', max: 6500},
-            { name: '管理(Administration)', max: 16000},
-            { name: '信息技术(Information Techology)', max: 30000},
-            { name: '客服(Customer Support)', max: 38000},
-            { name: '研发(Development)', max: 52000},
-            { name: '市场(Marketing)', max: 25000}
+            { name: 'sales', max: 6500},
+            { name: 'Administration', max: 16000},
+            { name: 'Information Techology', max: 30000},
+            { name: 'Customer Support', max: 38000},
+            { name: 'Development', max: 52000},
+            { name: 'Marketing', max: 25000}
         ]
     },
     series: [{
@@ -36,11 +36,11 @@ const option = {
         data: [
             {
                 value: [4300, 10000, 28000, 35000, 50000, 19000],
-                name: '预算分配(Allocated Budget)'
+                name: 'Allocated Budget'
             },
             {
                 value: [5000, 14000, 28000, 31000, 42000, 21000],
-                name: '实际开销(Actual Spending)'
+                name: 'Actual Spending'
             }
         ]
     }]


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