You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2019/12/12 14:18:43 UTC

[incubator-echarts] branch release updated (dab7cd2 -> f4fc1b0)

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

sushuang pushed a change to branch release
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git.


    from dab7cd2  fix: remove the page event. But use another way: keep trigger mouse move and mouse up event when dragging instead.
     new 34a1a21  fix: fix syntax for legacy IE.
     new 7466e4a  release: 4.6.0
     new f4fc1b0  release: upgrade zrender in package-lock.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 dist/echarts-en.common.js                  | 2564 +++++++++++++++++--------
 dist/echarts-en.common.min.js              |    2 +-
 dist/echarts-en.js                         | 2878 ++++++++++++++++++++--------
 dist/echarts-en.js.map                     |    2 +-
 dist/echarts-en.min.js                     |    2 +-
 dist/echarts-en.simple.js                  | 2367 ++++++++++++++++-------
 dist/echarts-en.simple.min.js              |    2 +-
 dist/echarts.common.js                     | 2564 +++++++++++++++++--------
 dist/echarts.common.min.js                 |    2 +-
 dist/echarts.js                            | 2878 ++++++++++++++++++++--------
 dist/echarts.js.map                        |    2 +-
 dist/echarts.min.js                        |    2 +-
 dist/echarts.simple.js                     | 2367 ++++++++++++++++-------
 dist/echarts.simple.min.js                 |    2 +-
 package-lock.json                          |    8 +-
 package.json                               |    4 +-
 src/component/toolbox/feature/MagicType.js |    2 +-
 src/echarts.js                             |    4 +-
 theme/roma.js                              |   20 +
 theme/tool/thumb.html                      |   20 +
 20 files changed, 11047 insertions(+), 4645 deletions(-)


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


[incubator-echarts] 01/03: fix: fix syntax for legacy IE.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch release
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit 34a1a213ba2013f1197f53774df53fbd4a0d02a6
Author: SHUANG SU <su...@gmail.com>
AuthorDate: Thu Dec 12 17:18:56 2019 +0800

    fix: fix syntax for legacy IE.
---
 src/component/toolbox/feature/MagicType.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/component/toolbox/feature/MagicType.js b/src/component/toolbox/feature/MagicType.js
index beeb006..326db37 100644
--- a/src/component/toolbox/feature/MagicType.js
+++ b/src/component/toolbox/feature/MagicType.js
@@ -37,7 +37,7 @@ MagicType.defaultOption = {
         /* eslint-disable */
         line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
         bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7',
-        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z', // jshint ignore:line
+        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line
         /* eslint-enable */
     },
     // `line`, `bar`, `stack`, `tiled`


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


[incubator-echarts] 02/03: release: 4.6.0

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch release
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit 7466e4a3f35fd0179b10035350afa88bbbea90df
Author: SHUANG SU <su...@gmail.com>
AuthorDate: Thu Dec 12 19:01:50 2019 +0800

    release: 4.6.0
---
 dist/echarts-en.common.js     | 2564 +++++++++++++++++++++++++-----------
 dist/echarts-en.common.min.js |    2 +-
 dist/echarts-en.js            | 2878 +++++++++++++++++++++++++++++------------
 dist/echarts-en.js.map        |    2 +-
 dist/echarts-en.min.js        |    2 +-
 dist/echarts-en.simple.js     | 2367 +++++++++++++++++++++++----------
 dist/echarts-en.simple.min.js |    2 +-
 dist/echarts.common.js        | 2564 +++++++++++++++++++++++++-----------
 dist/echarts.common.min.js    |    2 +-
 dist/echarts.js               | 2878 +++++++++++++++++++++++++++++------------
 dist/echarts.js.map           |    2 +-
 dist/echarts.min.js           |    2 +-
 dist/echarts.simple.js        | 2367 +++++++++++++++++++++++----------
 dist/echarts.simple.min.js    |    2 +-
 package.json                  |    4 +-
 src/echarts.js                |    4 +-
 theme/roma.js                 |   20 +
 theme/tool/thumb.html         |   20 +
 18 files changed, 11042 insertions(+), 4640 deletions(-)

diff --git a/dist/echarts-en.common.js b/dist/echarts-en.common.js
index 913b65d..305307e 100644
--- a/dist/echarts-en.common.js
+++ b/dist/echarts-en.common.js
@@ -208,12 +208,17 @@ function detect(ua) {
         // default, so we dont check navigator.maxTouchPoints for them here.
         touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge,
         // <http://caniuse.com/#search=pointer%20event>.
-        pointerEventsSupported: 'onpointerdown' in window
-            // Firefox supports pointer but not by default, only MS browsers are reliable on pointer
+        pointerEventsSupported:
+            // (1) Firefox supports pointer but not by default, only MS browsers are reliable on pointer
             // events currently. So we dont use that on other browsers unless tested sufficiently.
-            // Although IE 10 supports pointer event, it use old style and is different from the
+            // For example, in iOS 13 Mobile Chromium 78, if the touching behavior starts page
+            // scroll, the `pointermove` event can not be fired any more. That will break some
+            // features like "pan horizontally to move something and pan vertically to page scroll".
+            // The horizontal pan probably be interrupted by the casually triggered page scroll.
+            // (2) Although IE 10 supports pointer event, it use old style and is different from the
             // standard. So we exclude that. (IE 10 is hardly used on touch device)
-            && (browser.edge || (browser.ie && browser.version >= 11)),
+            'onpointerdown' in window
+                && (browser.edge || (browser.ie && browser.version >= 11)),
         // passiveSupported: detectPassiveSupport()
         domSupported: typeof document !== 'undefined'
     };
@@ -1262,7 +1267,11 @@ function Draggable() {
     this.on('mousedown', this._dragStart, this);
     this.on('mousemove', this._drag, this);
     this.on('mouseup', this._dragEnd, this);
-    this.on('globalout', this._dragEnd, this);
+    // `mosuemove` and `mouseup` can be continue to fire when dragging.
+    // See [Drag outside] in `Handler.js`. So we do not need to trigger
+    // `_dragEnd` when globalout. That would brings better user experience.
+    // this.on('globalout', this._dragEnd, this);
+
     // this._dropTarget = null;
     // this._draggingTarget = null;
 
@@ -1363,7 +1372,7 @@ var arrySlice = Array.prototype.slice;
  *        param: {string} eventType
  *        param: {string|Object} query
  *        return: {boolean}
- * @param {Function} [eventProcessor.afterTrigger] Call after all handlers called.
+ * @param {Function} [eventProcessor.afterTrigger] Called after all handlers called.
  *        param: {string} eventType
  */
 var Eventful = function (eventProcessor) {
@@ -1413,8 +1422,10 @@ Eventful.prototype = {
     /**
      * Unbind a event.
      *
-     * @param {string} event The event name.
+     * @param {string} [event] The event name.
+     *        If no `event` input, "off" all listeners.
      * @param {Function} [handler] The event handler.
+     *        If no `handler` input, "off" all listeners of the `event`.
      */
     off: function (event, handler) {
         var _h = this._$handlers;
@@ -1572,6 +1583,7 @@ Eventful.prototype = {
     }
 };
 
+
 function normalizeQuery(host, query) {
     var eventProcessor = host._$eventProcessor;
     if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
@@ -1890,6 +1902,18 @@ function preparePointerTransformer(markers, saved) {
 }
 
 /**
+ * Find native event compat for legency IE.
+ * Should be called at the begining of a native event listener.
+ *
+ * @param {Event} [e] Mouse event or touch event or pointer event.
+ *        For lagency IE, we use `window.event` is used.
+ * @return {Event} The native event.
+ */
+function getNativeEvent(e) {
+    return e || window.event;
+}
+
+/**
  * Normalize the coordinates of the input event.
  *
  * Get the `e.zrX` and `e.zrY`, which are relative to the top-left of
@@ -1903,14 +1927,14 @@ function preparePointerTransformer(markers, saved) {
  * between the result coords and the parameters `el` and `calculate`.
  *
  * @param {HTMLElement} el DOM element.
- * @param {Event} [e] Mouse event or touch event. For lagency IE,
- *        do not need to input it and `window.event` is used.
+ * @param {Event} [e] See `getNativeEvent`.
  * @param {boolean} [calculate=false] Whether to force calculate
  *        the coordinates but not use ones provided by browser.
+ * @return {UIEvent} The normalized native UIEvent.
  */
 function normalizeEvent(el, e, calculate) {
 
-    e = e || window.event;
+    e = getNativeEvent(e);
 
     if (e.zrX != null) {
         return e;
@@ -1950,8 +1974,11 @@ function normalizeEvent(el, e, calculate) {
  * @param {HTMLElement} el
  * @param {string} name
  * @param {Function} handler
+ * @param {Object|boolean} opt If boolean, means `opt.capture`
+ * @param {boolean} [opt.capture=false]
+ * @param {boolean} [opt.passive=false]
  */
-function addEventListener(el, name, handler) {
+function addEventListener(el, name, handler, opt) {
     if (isDomLevel2) {
         // Reproduct the console warning:
         // [Violation] Added non-passive event listener to a scroll-blocking <some> event.
@@ -1974,16 +2001,24 @@ function addEventListener(el, name, handler) {
         //     // By default, the third param of el.addEventListener is `capture: false`.
         //     : void 0;
         // el.addEventListener(name, handler /* , opts */);
-        el.addEventListener(name, handler);
+        el.addEventListener(name, handler, opt);
     }
     else {
+        // For simplicity, do not implement `setCapture` for IE9-.
         el.attachEvent('on' + name, handler);
     }
 }
 
-function removeEventListener(el, name, handler) {
+/**
+ * Parameter are the same as `addEventListener`.
+ *
+ * Notice that if a listener is registered twice, one with capture and one without,
+ * remove each one separately. Removal of a capturing listener does not affect a
+ * non-capturing version of the same listener, and vice versa.
+ */
+function removeEventListener(el, name, handler, opt) {
     if (isDomLevel2) {
-        el.removeEventListener(name, handler);
+        el.removeEventListener(name, handler, opt);
     }
     else {
         el.detachEvent('on' + name, handler);
@@ -2138,6 +2173,65 @@ var recognizers = {
     // Only pinch currently.
 };
 
+/**
+ * [The interface between `Handler` and `HandlerProxy`]:
+ *
+ * The default `HandlerProxy` only support the common standard web environment
+ * (e.g., standalone browser, headless browser, embed browser in mobild APP, ...).
+ * But `HandlerProxy` can be replaced to support more non-standard environment
+ * (e.g., mini app), or to support more feature that the default `HandlerProxy`
+ * not provided (like echarts-gl did).
+ * So the interface between `Handler` and `HandlerProxy` should be stable. Do not
+ * make break changes util inevitable. The interface include the public methods
+ * of `Handler` and the events listed in `handlerNames` below, by which `HandlerProxy`
+ * drives `Handler`.
+ */
+
+/**
+ * [Drag outside]:
+ *
+ * That is, triggering `mousemove` and `mouseup` event when the pointer is out of the
+ * zrender area when dragging. That is important for the improvement of the user experience
+ * when dragging something near the boundary without being terminated unexpectedly.
+ *
+ * We originally consider to introduce new events like `pagemovemove` and `pagemouseup`
+ * to resolve this issue. But some drawbacks of it is described in
+ * https://github.com/ecomfe/zrender/pull/536#issuecomment-560286899
+ *
+ * Instead, we referenced the specifications:
+ * https://www.w3.org/TR/touch-events/#the-touchmove-event
+ * https://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#event-type-mousemove
+ * where the the mousemove/touchmove can be continue to fire if the user began a drag
+ * operation and the pointer has left the boundary. (for the mouse event, browsers
+ * only do it on `document` and when the pointer has left the boundary of the browser.)
+ *
+ * So the default `HandlerProxy` supports this feature similarly: if it is in the dragging
+ * state (see `pointerCapture` in `HandlerProxy`), the `mousemove` and `mouseup` continue
+ * to fire until release the pointer. That is implemented by listen to those event on
+ * `document`.
+ * If we implement some other `HandlerProxy` only for touch device, that would be easier.
+ * The touch event support this feature by default.
+ *
+ * Note:
+ * There might be some cases that the mouse event can not be
+ * received on `document`. For example,
+ * (A) `useCapture` is not supported and some user defined event listeners on the ancestor
+ * of zr dom throw Error .
+ * (B) `useCapture` is not supported Some user defined event listeners on the ancestor of
+ * zr dom call `stopPropagation`.
+ * In these cases, the `mousemove` event might be keep triggered event
+ * if the mouse is released. We try to reduce the side-effect in those cases.
+ * That is, do nothing (especially, `findHover`) in those cases. See `isOutsideBoundary`.
+ *
+ * Note:
+ * If `HandlerProxy` listens to `document` with `useCapture`, `HandlerProxy` needs to
+ * make sure `stopPropagation` and `preventDefault` doing nothing if and only if the event
+ * target is not zrender dom. Becuase it is dangerous to enable users to call them in
+ * `document` capture phase to prevent the propagation to any listener of the webpage.
+ * But they are needed to work when the pointer inside the zrender dom.
+ */
+
+
 var SILENT = 'silent';
 
 function makeEventPacket(eveType, targetInfo, event) {
@@ -2162,17 +2256,19 @@ function makeEventPacket(eveType, targetInfo, event) {
     };
 }
 
-function stopEvent(event) {
+function stopEvent() {
     stop(this.event);
 }
 
 function EmptyProxy() {}
 EmptyProxy.prototype.dispose = function () {};
 
+
 var handlerNames = [
     'click', 'dblclick', 'mousewheel', 'mouseout',
     'mouseup', 'mousedown', 'mousemove', 'contextmenu'
 ];
+
 /**
  * @alias module:zrender/Handler
  * @constructor
@@ -2229,7 +2325,6 @@ var Handler = function (storage, painter, proxy, painterRoot) {
      */
     this._gestureMgr;
 
-
     Draggable.call(this);
 
     this.setHandlerProxy(proxy);
@@ -2258,6 +2353,8 @@ Handler.prototype = {
         var x = event.zrX;
         var y = event.zrY;
 
+        var isOutside = isOutsideBoundary(this, x, y);
+
         var lastHovered = this._hovered;
         var lastHoveredTarget = lastHovered.target;
 
@@ -2270,7 +2367,7 @@ Handler.prototype = {
             lastHoveredTarget = lastHovered.target;
         }
 
-        var hovered = this._hovered = this.findHover(x, y);
+        var hovered = this._hovered = isOutside ? {x: x, y: y} : this.findHover(x, y);
         var hoveredTarget = hovered.target;
 
         var proxy = this.proxy;
@@ -2291,23 +2388,18 @@ Handler.prototype = {
     },
 
     mouseout: function (event) {
-        this.dispatchToElement(this._hovered, 'mouseout', event);
+        var eventControl = event.zrEventControl;
+        var zrIsToLocalDOM = event.zrIsToLocalDOM;
 
-        // There might be some doms created by upper layer application
-        // at the same level of painter.getViewportRoot() (e.g., tooltip
-        // dom created by echarts), where 'globalout' event should not
-        // be triggered when mouse enters these doms. (But 'mouseout'
-        // should be triggered at the original hovered element as usual).
-        var element = event.toElement || event.relatedTarget;
-        var innerDom;
-        do {
-            element = element && element.parentNode;
+        if (eventControl !== 'only_globalout') {
+            this.dispatchToElement(this._hovered, 'mouseout', event);
         }
-        while (element && element.nodeType !== 9 && !(
-            innerDom = element === this.painterRoot
-        ));
 
-        !innerDom && this.trigger('globalout', {event: event});
+        if (eventControl !== 'no_globalout') {
+            // FIXME: if the pointer moving from the extra doms to realy "outside",
+            // the `globalout` should have been triggered. But currently not.
+            !zrIsToLocalDOM && this.trigger('globalout', {type: 'globalout', event: event});
+        }
     },
 
     /**
@@ -2453,9 +2545,18 @@ Handler.prototype = {
 // Common handlers
 each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
     Handler.prototype[name] = function (event) {
-        // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
-        var hovered = this.findHover(event.zrX, event.zrY);
-        var hoveredTarget = hovered.target;
+        var x = event.zrX;
+        var y = event.zrY;
+        var isOutside = isOutsideBoundary(this, x, y);
+
+        var hovered;
+        var hoveredTarget;
+
+        if (name !== 'mouseup' || !isOutside) {
+            // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
+            hovered = this.findHover(x, y);
+            hoveredTarget = hovered.target;
+        }
 
         if (name === 'mousedown') {
             this._downEl = hoveredTarget;
@@ -2507,6 +2608,14 @@ function isHover(displayable, x, y) {
     return false;
 }
 
+/**
+ * See [Drag outside].
+ */
+function isOutsideBoundary(handlerInstance, x, y) {
+    var painter = handlerInstance.painter;
+    return x < 0 || x > painter.getWidth() || y < 0 || y > painter.getHeight();
+}
+
 mixin(Handler, Eventful);
 mixin(Handler, Draggable);
 
@@ -10810,30 +10919,55 @@ Animation.prototype = {
 
 mixin(Animation, Eventful);
 
+/* global document */
+
 var TOUCH_CLICK_DELAY = 300;
 
-var mouseHandlerNames = [
-    'click', 'dblclick', 'mousewheel', 'mouseout',
-    'mouseup', 'mousedown', 'mousemove', 'contextmenu'
-];
+var globalEventSupported = env$1.domSupported;
 
-var touchHandlerNames = [
-    'touchstart', 'touchend', 'touchmove'
-];
 
-var pointerEventNames = {
-    pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
+var localNativeListenerNames = (function () {
+    var mouseHandlerNames = [
+        'click', 'dblclick', 'mousewheel', 'mouseout',
+        'mouseup', 'mousedown', 'mousemove', 'contextmenu'
+    ];
+    var touchHandlerNames = [
+        'touchstart', 'touchend', 'touchmove'
+    ];
+    var pointerEventNameMap = {
+        pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
+    };
+    var pointerHandlerNames = map(mouseHandlerNames, function (name) {
+        var nm = name.replace('mouse', 'pointer');
+        return pointerEventNameMap.hasOwnProperty(nm) ? nm : name;
+    });
+
+    return {
+        mouse: mouseHandlerNames,
+        touch: touchHandlerNames,
+        pointer: pointerHandlerNames
+    };
+})();
+
+var globalNativeListenerNames = {
+    mouse: ['mousemove', 'mouseup'],
+    pointer: ['pointermove', 'pointerup']
 };
 
-var pointerHandlerNames = map(mouseHandlerNames, function (name) {
-    var nm = name.replace('mouse', 'pointer');
-    return pointerEventNames[nm] ? nm : name;
-});
 
 function eventNameFix(name) {
     return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' : name;
 }
 
+function isPointerFromTouch(event) {
+    var pointerType = event.pointerType;
+    return pointerType === 'pen' || pointerType === 'touch';
+}
+
+// function useMSGuesture(handlerProxy, event) {
+//     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
+// }
+
 // function onMSGestureChange(proxy, event) {
 //     if (event.translationX || event.translationY) {
 //         // mousemove is carried by MSGesture to reduce the sensitivity.
@@ -10853,117 +10987,174 @@ function eventNameFix(name) {
  * 1. Mobile browsers dispatch mouse events 300ms after touchend.
  * 2. Chrome for Android dispatch mousedown for long-touch about 650ms
  * Result: Blocking Mouse Events for 700ms.
+ *
+ * @param {DOMHandlerScope} scope
  */
-function setTouchTimer(instance) {
-    instance._touching = true;
-    clearTimeout(instance._touchTimer);
-    instance._touchTimer = setTimeout(function () {
-        instance._touching = false;
+function setTouchTimer(scope) {
+    scope.touching = true;
+    if (scope.touchTimer != null) {
+        clearTimeout(scope.touchTimer);
+        scope.touchTimer = null;
+    }
+    scope.touchTimer = setTimeout(function () {
+        scope.touching = false;
+        scope.touchTimer = null;
     }, 700);
 }
 
+// Mark touch, which is useful in distinguish touch and
+// mouse event in upper applicatoin.
+function markTouch(event) {
+    event && (event.zrByTouch = true);
+}
+
+
+// function markTriggeredFromLocal(event) {
+//     event && (event.__zrIsFromLocal = true);
+// }
+
+// function isTriggeredFromLocal(instance, event) {
+//     return !!(event && event.__zrIsFromLocal);
+// }
+
+function normalizeGlobalEvent(instance, event) {
+    // offsetX, offsetY still need to be calculated. They are necessary in the event
+    // handlers of the upper applications. Set `true` to force calculate them.
+    return normalizeEvent(instance.dom, new FakeGlobalEvent(instance, event), true);
+}
+
+/**
+ * Detect whether the given el is in `painterRoot`.
+ */
+function isLocalEl(instance, el) {
+    var isLocal = false;
+    do {
+        el = el && el.parentNode;
+    }
+    while (el && el.nodeType !== 9 && !(
+        isLocal = el === instance.painterRoot
+    ));
+    return isLocal;
+}
+
+/**
+ * Make a fake event but not change the original event,
+ * becuase the global event probably be used by other
+ * listeners not belonging to zrender.
+ * @class
+ */
+function FakeGlobalEvent(instance, event) {
+    this.type = event.type;
+    this.target = this.currentTarget = instance.dom;
+    this.pointerType = event.pointerType;
+    // Necessray for the force calculation of zrX, zrY
+    this.clientX = event.clientX;
+    this.clientY = event.clientY;
+    // Because we do not mount global listeners to touch events,
+    // we do not copy `targetTouches` and `changedTouches` here.
+}
+var fakeGlobalEventProto = FakeGlobalEvent.prototype;
+// we make the default methods on the event do nothing,
+// otherwise it is dangerous. See more details in
+// [Drag outside] in `Handler.js`.
+fakeGlobalEventProto.stopPropagation =
+    fakeGlobalEventProto.stopImmediatePropagation =
+    fakeGlobalEventProto.preventDefault = noop;
+
+
+/**
+ * Local DOM Handlers
+ * @this {HandlerProxy}
+ */
+var localDOMHandlers = {
+
+    mousedown: function (event) {
+        event = normalizeEvent(this.dom, event);
+
+        this._mayPointerCapture = [event.zrX, event.zrY];
+
+        this.trigger('mousedown', event);
+    },
 
-var domHandlers = {
-    /**
-     * Mouse move handler
-     * @inner
-     * @param {Event} event
-     */
     mousemove: function (event) {
         event = normalizeEvent(this.dom, event);
 
+        var downPoint = this._mayPointerCapture;
+        if (downPoint && (event.zrX !== downPoint[0] || event.zrY !== downPoint[1])) {
+            togglePointerCapture(this, true);
+        }
+
         this.trigger('mousemove', event);
     },
 
-    /**
-     * Mouse out handler
-     * @inner
-     * @param {Event} event
-     */
-    mouseout: function (event) {
+    mouseup: function (event) {
         event = normalizeEvent(this.dom, event);
 
-        var element = event.toElement || event.relatedTarget;
-        if (element !== this.dom) {
-            while (element && element.nodeType !== 9) {
-                // 忽略包含在root中的dom引起的mouseOut
-                if (element === this.dom) {
-                    return;
-                }
+        togglePointerCapture(this, false);
 
-                element = element.parentNode;
-            }
+        this.trigger('mouseup', event);
+    },
+
+    mouseout: function (event) {
+        event = normalizeEvent(this.dom, event);
+
+        // Similarly to the browser did on `document` and touch event,
+        // `globalout` will be delayed to final pointer cature release.
+        if (this._pointerCapturing) {
+            event.zrEventControl = 'no_globalout';
         }
 
+        // There might be some doms created by upper layer application
+        // at the same level of painter.getViewportRoot() (e.g., tooltip
+        // dom created by echarts), where 'globalout' event should not
+        // be triggered when mouse enters these doms. (But 'mouseout'
+        // should be triggered at the original hovered element as usual).
+        var element = event.toElement || event.relatedTarget;
+        event.zrIsToLocalDOM = isLocalEl(this, element);
+
         this.trigger('mouseout', event);
     },
 
-    /**
-     * Touch开始响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchstart: function (event) {
         // Default mouse behaviour should not be disabled here.
         // For example, page may needs to be slided.
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this._lastTouchMoment = new Date();
 
         this.handler.processGesture(event, 'start');
 
-        // In touch device, trigger `mousemove`(`mouseover`) should
-        // be triggered, and must before `mousedown` triggered.
-        domHandlers.mousemove.call(this, event);
-
-        domHandlers.mousedown.call(this, event);
-
-        setTouchTimer(this);
+        // For consistent event listener for both touch device and mouse device,
+        // we simulate "mouseover-->mousedown" in touch device. So we trigger
+        // `mousemove` here (to trigger `mouseover` inside), and then trigger
+        // `mousedown`.
+        localDOMHandlers.mousemove.call(this, event);
+        localDOMHandlers.mousedown.call(this, event);
     },
 
-    /**
-     * Touch移动响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchmove: function (event) {
-
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this.handler.processGesture(event, 'change');
 
         // Mouse move should always be triggered no matter whether
         // there is gestrue event, because mouse move and pinch may
         // be used at the same time.
-        domHandlers.mousemove.call(this, event);
-
-        setTouchTimer(this);
+        localDOMHandlers.mousemove.call(this, event);
     },
 
-    /**
-     * Touch结束响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchend: function (event) {
-
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this.handler.processGesture(event, 'end');
 
-        domHandlers.mouseup.call(this, event);
+        localDOMHandlers.mouseup.call(this, event);
 
         // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is
         // triggered in `touchstart`. This seems to be illogical, but by this mechanism,
@@ -10976,14 +11167,12 @@ var domHandlers = {
         // click event should always be triggered no matter whether
         // there is gestrue event. System click can not be prevented.
         if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) {
-            domHandlers.click.call(this, event);
+            localDOMHandlers.click.call(this, event);
         }
-
-        setTouchTimer(this);
     },
 
     pointerdown: function (event) {
-        domHandlers.mousedown.call(this, event);
+        localDOMHandlers.mousedown.call(this, event);
 
         // if (useMSGuesture(this, event)) {
         //     this._msGesture.addPointer(event.pointerId);
@@ -10997,12 +11186,12 @@ var domHandlers = {
         // upper application. So, we dont support mousemove on MS touch
         // device yet.
         if (!isPointerFromTouch(event)) {
-            domHandlers.mousemove.call(this, event);
+            localDOMHandlers.mousemove.call(this, event);
         }
     },
 
     pointerup: function (event) {
-        domHandlers.mouseup.call(this, event);
+        localDOMHandlers.mouseup.call(this, event);
     },
 
     pointerout: function (event) {
@@ -11010,80 +11199,77 @@ var domHandlers = {
         // (IE11+/Edge on MS Surface) after click event triggered,
         // which is inconsistent with the mousout behavior we defined
         // in touchend. So we unify them.
-        // (check domHandlers.touchend for detailed explanation)
+        // (check localDOMHandlers.touchend for detailed explanation)
         if (!isPointerFromTouch(event)) {
-            domHandlers.mouseout.call(this, event);
+            localDOMHandlers.mouseout.call(this, event);
         }
     }
-};
-
-function isPointerFromTouch(event) {
-    var pointerType = event.pointerType;
-    return pointerType === 'pen' || pointerType === 'touch';
-}
 
-// function useMSGuesture(handlerProxy, event) {
-//     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
-// }
+};
 
-// Common handlers
-each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
-    domHandlers[name] = function (event) {
+/**
+ * Othere DOM UI Event handlers for zr dom.
+ * @this {HandlerProxy}
+ */
+each$1(['click', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
+    localDOMHandlers[name] = function (event) {
         event = normalizeEvent(this.dom, event);
         this.trigger(name, event);
     };
 });
 
+
 /**
- * 为控制类实例初始化dom 事件处理函数
+ * DOM UI Event handlers for global page.
  *
- * @inner
- * @param {module:zrender/Handler} instance 控制类实例
+ * [Caution]:
+ * those handlers should both support in capture phase and bubble phase!
+ *
+ * @this {HandlerProxy}
  */
-function initDomHandler(instance) {
-    each$1(touchHandlerNames, function (name) {
-        instance._handlers[name] = bind(domHandlers[name], instance);
-    });
+var globalDOMHandlers = {
 
-    each$1(pointerHandlerNames, function (name) {
-        instance._handlers[name] = bind(domHandlers[name], instance);
-    });
+    pointermove: function (event) {
+        // FIXME
+        // pointermove is so sensitive that it always triggered when
+        // tap(click) on touch screen, which affect some judgement in
+        // upper application. So, we dont support mousemove on MS touch
+        // device yet.
+        if (!isPointerFromTouch(event)) {
+            globalDOMHandlers.mousemove.call(this, event);
+        }
+    },
 
-    each$1(mouseHandlerNames, function (name) {
-        instance._handlers[name] = makeMouseHandler(domHandlers[name], instance);
-    });
+    pointerup: function (event) {
+        globalDOMHandlers.mouseup.call(this, event);
+    },
 
-    function makeMouseHandler(fn, instance) {
-        return function () {
-            if (instance._touching) {
-                return;
-            }
-            return fn.apply(instance, arguments);
-        };
-    }
-}
+    mousemove: function (event) {
+        this.trigger('mousemove', event);
+    },
 
+    mouseup: function (event) {
+        var pointerCaptureReleasing = this._pointerCapturing;
 
-function HandlerDomProxy(dom) {
-    Eventful.call(this);
+        togglePointerCapture(this, false);
 
-    this.dom = dom;
+        this.trigger('mouseup', event);
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._touching = false;
+        if (pointerCaptureReleasing) {
+            event.zrEventControl = 'only_globalout';
+            this.trigger('mouseout', event);
+        }
+    }
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this._touchTimer;
+};
 
-    this._handlers = {};
 
-    initDomHandler(this);
+/**
+ * @param {HandlerProxy} instance
+ * @param {DOMHandlerScope} scope
+ */
+function mountLocalDOMEventListeners(instance, scope) {
+    var domHandlers = scope.domHandlers;
 
     if (env$1.pointerEventsSupported) { // Only IE11+/Edge
         // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240),
@@ -11092,7 +11278,12 @@ function HandlerDomProxy(dom) {
         // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on
         // screen, which do not occurs in pointer event.
         // So we use pointer event to both detect touch gesture and mouse behavior.
-        mountHandlers(pointerHandlerNames, this);
+        each$1(localNativeListenerNames.pointer, function (nativeEventName) {
+            mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                // markTriggeredFromLocal(event);
+                domHandlers[nativeEventName].call(instance, event);
+            });
+        });
 
         // FIXME
         // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable,
@@ -11111,7 +11302,13 @@ function HandlerDomProxy(dom) {
     }
     else {
         if (env$1.touchEventsSupported) {
-            mountHandlers(touchHandlerNames, this);
+            each$1(localNativeListenerNames.touch, function (nativeEventName) {
+                mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                    // markTriggeredFromLocal(event);
+                    domHandlers[nativeEventName].call(instance, event);
+                    setTouchTimer(scope);
+                });
+            });
             // Handler of 'mouseout' event is needed in touch mode, which will be mounted below.
             // addEventListener(root, 'mouseout', this._mouseoutHandler);
         }
@@ -11121,23 +11318,145 @@ function HandlerDomProxy(dom) {
         // mouse event can not be handle in those devices.
         // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent
         // mouseevent after touch event triggered, see `setTouchTimer`.
-        mountHandlers(mouseHandlerNames, this);
+        each$1(localNativeListenerNames.mouse, function (nativeEventName) {
+            mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                event = getNativeEvent(event);
+                if (!scope.touching) {
+                    // markTriggeredFromLocal(event);
+                    domHandlers[nativeEventName].call(instance, event);
+                }
+            });
+        });
+    }
+}
+
+/**
+ * @param {HandlerProxy} instance
+ * @param {DOMHandlerScope} scope
+ */
+function mountGlobalDOMEventListeners(instance, scope) {
+    // Only IE11+/Edge. See the comment in `mountLocalDOMEventListeners`.
+    if (env$1.pointerEventsSupported) {
+        each$1(globalNativeListenerNames.pointer, mount);
+    }
+    // Touch event has implemented "drag outside" so we do not mount global listener for touch event.
+    // (see https://www.w3.org/TR/touch-events/#the-touchmove-event)
+    // We do not consider "both-support-touch-and-mouse device" for this feature (see the comment of
+    // `mountLocalDOMEventListeners`) to avoid bugs util some requirements come.
+    else if (!env$1.touchEventsSupported) {
+        each$1(globalNativeListenerNames.mouse, mount);
+    }
+
+    function mount(nativeEventName) {
+        function nativeEventListener(event) {
+            event = getNativeEvent(event);
+            // See the reason in [Drag outside] in `Handler.js`
+            // This checking supports both `useCapture` or not.
+            // PENDING: if there is performance issue in some devices,
+            // we probably can not use `useCapture` and change a easier
+            // to judes whether local (mark).
+            if (!isLocalEl(instance, event.target)) {
+                event = normalizeGlobalEvent(instance, event);
+                scope.domHandlers[nativeEventName].call(instance, event);
+            }
+        }
+        mountSingleDOMEventListener(
+            scope, nativeEventName, nativeEventListener,
+            {capture: true} // See [Drag Outside] in `Handler.js`
+        );
+    }
+}
+
+function mountSingleDOMEventListener(scope, nativeEventName, listener, opt) {
+    scope.mounted[nativeEventName] = listener;
+    scope.listenerOpts[nativeEventName] = opt;
+    addEventListener(scope.domTarget, eventNameFix(nativeEventName), listener, opt);
+}
+
+function unmountDOMEventListeners(scope) {
+    var mounted = scope.mounted;
+    for (var nativeEventName in mounted) {
+        if (mounted.hasOwnProperty(nativeEventName)) {
+            removeEventListener(
+                scope.domTarget, eventNameFix(nativeEventName), mounted[nativeEventName],
+                scope.listenerOpts[nativeEventName]
+            );
+        }
+    }
+    scope.mounted = {};
+}
+
+/**
+ * See [Drag Outside] in `Handler.js`.
+ * @implement
+ * @param {boolean} isPointerCapturing Should never be `null`/`undefined`.
+ *        `true`: start to capture pointer if it is not capturing.
+ *        `false`: end the capture if it is capturing.
+ */
+function togglePointerCapture(instance, isPointerCapturing) {
+    instance._mayPointerCapture = null;
+
+    if (globalEventSupported && (instance._pointerCapturing ^ isPointerCapturing)) {
+        instance._pointerCapturing = isPointerCapturing;
+
+        var globalHandlerScope = instance._globalHandlerScope;
+        isPointerCapturing
+            ? mountGlobalDOMEventListeners(instance, globalHandlerScope)
+            : unmountDOMEventListeners(globalHandlerScope);
     }
+}
+
+/**
+ * @inner
+ * @class
+ */
+function DOMHandlerScope(domTarget, domHandlers) {
+    this.domTarget = domTarget;
+    this.domHandlers = domHandlers;
+
+    // Key: eventName, value: mounted handler funcitons.
+    // Used for unmount.
+    this.mounted = {};
+    this.listenerOpts = {};
+
+    this.touchTimer = null;
+    this.touching = false;
+}
+
+/**
+ * @public
+ * @class
+ */
+function HandlerDomProxy(dom, painterRoot) {
+    Eventful.call(this);
+
+    this.dom = dom;
+    this.painterRoot = painterRoot;
 
-    function mountHandlers(handlerNames, instance) {
-        each$1(handlerNames, function (name) {
-            addEventListener(dom, eventNameFix(name), instance._handlers[name]);
-        }, instance);
+    this._localHandlerScope = new DOMHandlerScope(dom, localDOMHandlers);
+
+    if (globalEventSupported) {
+        this._globalHandlerScope = new DOMHandlerScope(document, globalDOMHandlers);
     }
+
+    /**
+     * @type {boolean}
+     */
+    this._pointerCapturing = false;
+    /**
+     * @type {Array.<number>} [x, y] or null.
+     */
+    this._mayPointerCapture = null;
+
+    mountLocalDOMEventListeners(this, this._localHandlerScope);
 }
 
 var handlerDomProxyProto = HandlerDomProxy.prototype;
-handlerDomProxyProto.dispose = function () {
-    var handlerNames = mouseHandlerNames.concat(touchHandlerNames);
 
-    for (var i = 0; i < handlerNames.length; i++) {
-        var name = handlerNames[i];
-        removeEventListener(this.dom, eventNameFix(name), this._handlers[name]);
+handlerDomProxyProto.dispose = function () {
+    unmountDOMEventListeners(this._localHandlerScope);
+    if (globalEventSupported) {
+        unmountDOMEventListeners(this._globalHandlerScope);
     }
 };
 
@@ -11145,6 +11464,7 @@ handlerDomProxyProto.setCursor = function (cursorStyle) {
     this.dom.style && (this.dom.style.cursor = cursorStyle || 'default');
 };
 
+
 mixin(HandlerDomProxy, Eventful);
 
 /*!
@@ -11168,7 +11488,7 @@ var instances$1 = {};    // ZRender实例map索引
 /**
  * @type {string}
  */
-var version$1 = '4.1.2';
+var version$1 = '4.2.0';
 
 /**
  * Initializing a zrender instance
@@ -11270,7 +11590,7 @@ var ZRender = function (id, dom, opts) {
     this.storage = storage;
     this.painter = painter;
 
-    var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot()) : null;
+    var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot(), painter.root) : null;
     this.handler = new Handler(storage, painter, handerProxy, painter.root);
 
     /**
@@ -17782,8 +18102,7 @@ function rollbackDefaultTextStyle(style) {
 }
 
 function getFont(opt, ecModel) {
-    // ecModel or default text style model.
-    var gTextStyleModel = ecModel || ecModel.getModel('textStyle');
+    var gTextStyleModel = ecModel && ecModel.getModel('textStyle');
     return trim([
         // FIXME in node-canvas fontWeight is before fontStyle
         opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '',
@@ -20493,178 +20812,6 @@ var colorPaletteMixin = {
 * under the License.
 */
 
-/**
- * Helper for model references.
- * There are many manners to refer axis/coordSys.
- */
-
-// TODO
-// merge relevant logic to this file?
-// check: "modelHelper" of tooltip and "BrushTargetManager".
-
-/**
- * @return {Object} For example:
- * {
- *     coordSysName: 'cartesian2d',
- *     coordSysDims: ['x', 'y', ...],
- *     axisMap: HashMap({
- *         x: xAxisModel,
- *         y: yAxisModel
- *     }),
- *     categoryAxisMap: HashMap({
- *         x: xAxisModel,
- *         y: undefined
- *     }),
- *     // It also indicate that whether there is category axis.
- *     firstCategoryDimIndex: 1,
- *     // To replace user specified encode.
- * }
- */
-function getCoordSysDefineBySeries(seriesModel) {
-    var coordSysName = seriesModel.get('coordinateSystem');
-    var result = {
-        coordSysName: coordSysName,
-        coordSysDims: [],
-        axisMap: createHashMap(),
-        categoryAxisMap: createHashMap()
-    };
-    var fetch = fetchers[coordSysName];
-    if (fetch) {
-        fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
-        return result;
-    }
-}
-
-var fetchers = {
-
-    cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var xAxisModel = seriesModel.getReferringComponents('xAxis')[0];
-        var yAxisModel = seriesModel.getReferringComponents('yAxis')[0];
-
-        if (__DEV__) {
-            if (!xAxisModel) {
-                throw new Error('xAxis "' + retrieve(
-                    seriesModel.get('xAxisIndex'),
-                    seriesModel.get('xAxisId'),
-                    0
-                ) + '" not found');
-            }
-            if (!yAxisModel) {
-                throw new Error('yAxis "' + retrieve(
-                    seriesModel.get('xAxisIndex'),
-                    seriesModel.get('yAxisId'),
-                    0
-                ) + '" not found');
-            }
-        }
-
-        result.coordSysDims = ['x', 'y'];
-        axisMap.set('x', xAxisModel);
-        axisMap.set('y', yAxisModel);
-
-        if (isCategory(xAxisModel)) {
-            categoryAxisMap.set('x', xAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-        if (isCategory(yAxisModel)) {
-            categoryAxisMap.set('y', yAxisModel);
-            result.firstCategoryDimIndex = 1;
-        }
-    },
-
-    singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0];
-
-        if (__DEV__) {
-            if (!singleAxisModel) {
-                throw new Error('singleAxis should be specified.');
-            }
-        }
-
-        result.coordSysDims = ['single'];
-        axisMap.set('single', singleAxisModel);
-
-        if (isCategory(singleAxisModel)) {
-            categoryAxisMap.set('single', singleAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-    },
-
-    polar: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var polarModel = seriesModel.getReferringComponents('polar')[0];
-        var radiusAxisModel = polarModel.findAxisModel('radiusAxis');
-        var angleAxisModel = polarModel.findAxisModel('angleAxis');
-
-        if (__DEV__) {
-            if (!angleAxisModel) {
-                throw new Error('angleAxis option not found');
-            }
-            if (!radiusAxisModel) {
-                throw new Error('radiusAxis option not found');
-            }
-        }
-
-        result.coordSysDims = ['radius', 'angle'];
-        axisMap.set('radius', radiusAxisModel);
-        axisMap.set('angle', angleAxisModel);
-
-        if (isCategory(radiusAxisModel)) {
-            categoryAxisMap.set('radius', radiusAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-        if (isCategory(angleAxisModel)) {
-            categoryAxisMap.set('angle', angleAxisModel);
-            result.firstCategoryDimIndex = 1;
-        }
-    },
-
-    geo: function (seriesModel, result, axisMap, categoryAxisMap) {
-        result.coordSysDims = ['lng', 'lat'];
-    },
-
-    parallel: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var ecModel = seriesModel.ecModel;
-        var parallelModel = ecModel.getComponent(
-            'parallel', seriesModel.get('parallelIndex')
-        );
-        var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice();
-
-        each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) {
-            var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
-            var axisDim = coordSysDims[index];
-            axisMap.set(axisDim, axisModel);
-
-            if (isCategory(axisModel) && result.firstCategoryDimIndex == null) {
-                categoryAxisMap.set(axisDim, axisModel);
-                result.firstCategoryDimIndex = index;
-            }
-        });
-    }
-};
-
-function isCategory(axisModel) {
-    return axisModel.get('type') === 'category';
-}
-
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
 // Avoid typo.
 var SOURCE_FORMAT_ORIGINAL = 'original';
 var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows';
@@ -20834,6 +20981,13 @@ enableClassCheck(Source);
 * under the License.
 */
 
+// The result of `guessOrdinal`.
+var BE_ORDINAL = {
+    Must: 1, // Encounter string but not '-' and not number-like.
+    Might: 2, // Encounter string but number-like.
+    Not: 3 // Other cases
+};
+
 var inner$3 = makeInner();
 
 /**
@@ -20967,14 +21121,6 @@ function prepareSource(seriesModel) {
         data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine
     );
 
-    // Note: dataset option does not have `encode`.
-    var encodeDefine = seriesOption.encode;
-    if (!encodeDefine && datasetModel) {
-        encodeDefine = makeDefaultEncode(
-            seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
-        );
-    }
-
     inner$3(seriesModel).source = new Source({
         data: data,
         fromDataset: fromDataset,
@@ -20983,7 +21129,8 @@ function prepareSource(seriesModel) {
         dimensionsDefine: completeResult.dimensionsDefine,
         startIndex: completeResult.startIndex,
         dimensionsDetectCount: completeResult.dimensionsDetectCount,
-        encodeDefine: encodeDefine
+        // Note: dataset option does not have `encode`.
+        encodeDefine: seriesOption.encode
     });
 }
 
@@ -20995,7 +21142,6 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
 
     var dimensionsDetectCount;
     var startIndex;
-    var findPotentialName;
 
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
         // Rule: Most of the first line are string: it is header.
@@ -21038,13 +21184,11 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
         if (!dimensionsDefine) {
             dimensionsDefine = objectRowsCollectDimensions(data);
-            findPotentialName = true;
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
         if (!dimensionsDefine) {
             dimensionsDefine = [];
-            findPotentialName = true;
             each$1(data, function (colArr, key) {
                 dimensionsDefine.push(key);
             });
@@ -21060,21 +21204,10 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
         }
     }
 
-    var potentialNameDimIndex;
-    if (findPotentialName) {
-        each$1(dimensionsDefine, function (dim, idx) {
-            if ((isObject$1(dim) ? dim.name : dim) === 'name') {
-                potentialNameDimIndex = idx;
-            }
-        });
-    }
-
     return {
         startIndex: startIndex,
         dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
-        dimensionsDetectCount: dimensionsDetectCount,
-        potentialNameDimIndex: potentialNameDimIndex
-        // TODO: potentialIdDimIdx
+        dimensionsDetectCount: dimensionsDetectCount
     };
 }
 
@@ -21148,103 +21281,198 @@ function objectRowsCollectDimensions(data) {
     }
 }
 
-// ??? TODO merge to completedimensions, where also has
-// default encode making logic. And the default rule
-// should depends on series? consider 'map'.
-function makeDefaultEncode(
-    seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
-) {
-    var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
+/**
+ * [The strategy of the arrengment of data dimensions for dataset]:
+ * "value way": all axes are non-category axes. So series one by one take
+ *     several (the number is coordSysDims.length) dimensions from dataset.
+ *     The result of data arrengment of data dimensions like:
+ *     | ser0_x | ser0_y | ser1_x | ser1_y | ser2_x | ser2_y |
+ * "category way": at least one axis is category axis. So the the first data
+ *     dimension is always mapped to the first category axis and shared by
+ *     all of the series. The other data dimensions are taken by series like
+ *     "value way" does.
+ *     The result of data arrengment of data dimensions like:
+ *     | ser_shared_x | ser0_y | ser1_y | ser2_y |
+ *
+ * @param {Array.<Object|string>} coordDimensions [{name: <string>, type: <string>, dimsDef: <Array>}, ...]
+ * @param {module:model/Series} seriesModel
+ * @param {module:data/Source} source
+ * @return {Object} encode Never be `null/undefined`.
+ */
+function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, source) {
     var encode = {};
-    // var encodeTooltip = [];
-    // var encodeLabel = [];
+
+    var datasetModel = getDatasetModel(seriesModel);
+    // Currently only make default when using dataset, util more reqirements occur.
+    if (!datasetModel || !coordDimensions) {
+        return encode;
+    }
+
     var encodeItemName = [];
     var encodeSeriesName = [];
-    var seriesType = seriesModel.subType;
-
-    // ??? TODO refactor: provide by series itself.
-    // Consider the case: 'map' series is based on geo coordSys,
-    // 'graph', 'heatmap' can be based on cartesian. But can not
-    // give default rule simply here.
-    var nSeriesMap = createHashMap(['pie', 'map', 'funnel']);
-    var cSeriesMap = createHashMap([
-        'line', 'bar', 'pictorialBar', 'scatter', 'effectScatter', 'candlestick', 'boxplot'
-    ]);
-
-    // Usually in this case series will use the first data
-    // dimension as the "value" dimension, or other default
-    // processes respectively.
-    if (coordSysDefine && cSeriesMap.get(seriesType) != null) {
-        var ecModel = seriesModel.ecModel;
-        var datasetMap = inner$3(ecModel).datasetMap;
-        var key = datasetModel.uid + '_' + seriesLayoutBy;
-        var datasetRecord = datasetMap.get(key)
-            || datasetMap.set(key, {categoryWayDim: 1, valueWayDim: 0});
 
-        // TODO
-        // Auto detect first time axis and do arrangement.
-        each$1(coordSysDefine.coordSysDims, function (coordDim) {
-            // In value way.
-            if (coordSysDefine.firstCategoryDimIndex == null) {
-                var dataDim = datasetRecord.valueWayDim++;
-                encode[coordDim] = dataDim;
-
-                // ??? TODO give a better default series name rule?
-                // especially when encode x y specified.
-                // consider: when mutiple series share one dimension
-                // category axis, series name should better use
-                // the other dimsion name. On the other hand, use
-                // both dimensions name.
-
-                encodeSeriesName.push(dataDim);
-                // encodeTooltip.push(dataDim);
-                // encodeLabel.push(dataDim);
-            }
-            // In category way, category axis.
-            else if (coordSysDefine.categoryAxisMap.get(coordDim)) {
-                encode[coordDim] = 0;
-                encodeItemName.push(0);
-            }
-            // In category way, non-category axis.
-            else {
-                var dataDim = datasetRecord.categoryWayDim++;
-                encode[coordDim] = dataDim;
-                // encodeTooltip.push(dataDim);
-                // encodeLabel.push(dataDim);
-                encodeSeriesName.push(dataDim);
+    var ecModel = seriesModel.ecModel;
+    var datasetMap = inner$3(ecModel).datasetMap;
+    var key = datasetModel.uid + '_' + source.seriesLayoutBy;
+
+    var baseCategoryDimIndex;
+    var categoryWayValueDimStart;
+    coordDimensions = coordDimensions.slice();
+    each$1(coordDimensions, function (coordDimInfo, coordDimIdx) {
+        !isObject$1(coordDimInfo) && (coordDimensions[coordDimIdx] = {name: coordDimInfo});
+        if (coordDimInfo.type === 'ordinal' && baseCategoryDimIndex == null) {
+            baseCategoryDimIndex = coordDimIdx;
+            categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimensions[coordDimIdx]);
+        }
+        encode[coordDimInfo.name] = [];
+    });
+
+    var datasetRecord = datasetMap.get(key)
+        || datasetMap.set(key, {categoryWayDim: categoryWayValueDimStart, valueWayDim: 0});
+
+    // TODO
+    // Auto detect first time axis and do arrangement.
+    each$1(coordDimensions, function (coordDimInfo, coordDimIdx) {
+        var coordDimName = coordDimInfo.name;
+        var count = getDataDimCountOnCoordDim(coordDimInfo);
+
+        // In value way.
+        if (baseCategoryDimIndex == null) {
+            var start = datasetRecord.valueWayDim;
+            pushDim(encode[coordDimName], start, count);
+            pushDim(encodeSeriesName, start, count);
+            datasetRecord.valueWayDim += count;
+
+            // ??? TODO give a better default series name rule?
+            // especially when encode x y specified.
+            // consider: when mutiple series share one dimension
+            // category axis, series name should better use
+            // the other dimsion name. On the other hand, use
+            // both dimensions name.
+        }
+        // In category way, the first category axis.
+        else if (baseCategoryDimIndex === coordDimIdx) {
+            pushDim(encode[coordDimName], 0, count);
+            pushDim(encodeItemName, 0, count);
+        }
+        // In category way, the other axis.
+        else {
+            var start = datasetRecord.categoryWayDim;
+            pushDim(encode[coordDimName], start, count);
+            pushDim(encodeSeriesName, start, count);
+            datasetRecord.categoryWayDim += count;
+        }
+    });
+
+    function pushDim(dimIdxArr, idxFrom, idxCount) {
+        for (var i = 0; i < idxCount; i++) {
+            dimIdxArr.push(idxFrom + i);
+        }
+    }
+
+    function getDataDimCountOnCoordDim(coordDimInfo) {
+        var dimsDef = coordDimInfo.dimsDef;
+        return dimsDef ? dimsDef.length : 1;
+    }
+
+    encodeItemName.length && (encode.itemName = encodeItemName);
+    encodeSeriesName.length && (encode.seriesName = encodeSeriesName);
+
+    return encode;
+}
+
+/**
+ * Work for data like [{name: ..., value: ...}, ...].
+ *
+ * @param {module:model/Series} seriesModel
+ * @param {module:data/Source} source
+ * @return {Object} encode Never be `null/undefined`.
+ */
+function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
+    var encode = {};
+
+    var datasetModel = getDatasetModel(seriesModel);
+    // Currently only make default when using dataset, util more reqirements occur.
+    if (!datasetModel) {
+        return encode;
+    }
+
+    var sourceFormat = source.sourceFormat;
+    var dimensionsDefine = source.dimensionsDefine;
+
+    var potentialNameDimIndex;
+    if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS || sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
+        each$1(dimensionsDefine, function (dim, idx) {
+            if ((isObject$1(dim) ? dim.name : dim) === 'name') {
+                potentialNameDimIndex = idx;
             }
         });
     }
-    // Do not make a complex rule! Hard to code maintain and not necessary.
-    // ??? TODO refactor: provide by series itself.
-    // [{name: ..., value: ...}, ...] like:
-    else if (nSeriesMap.get(seriesType) != null) {
-        // Find the first not ordinal. (5 is an experience value)
-        var firstNotOrdinal;
-        for (var i = 0; i < 5 && firstNotOrdinal == null; i++) {
-            if (!doGuessOrdinal(
-                data, sourceFormat, seriesLayoutBy,
-                completeResult.dimensionsDefine, completeResult.startIndex, i
-            )) {
-                firstNotOrdinal = i;
+
+    // idxResult: {v, n}.
+    var idxResult = (function () {
+
+        var idxRes0 = {};
+        var idxRes1 = {};
+        var guessRecords = [];
+
+        // 5 is an experience value.
+        for (var i = 0, len = Math.min(5, dimCount); i < len; i++) {
+            var guessResult = doGuessOrdinal(
+                source.data, sourceFormat, source.seriesLayoutBy,
+                dimensionsDefine, source.startIndex, i
+            );
+            guessRecords.push(guessResult);
+            var isPureNumber = guessResult === BE_ORDINAL.Not;
+
+            // [Strategy of idxRes0]: find the first BE_ORDINAL.Not as the value dim,
+            // and then find a name dim with the priority:
+            // "BE_ORDINAL.Might|BE_ORDINAL.Must" > "other dim" > "the value dim itself".
+            if (isPureNumber && idxRes0.v == null && i !== potentialNameDimIndex) {
+                idxRes0.v = i;
+            }
+            if (idxRes0.n == null
+                || (idxRes0.n === idxRes0.v)
+                || (!isPureNumber && guessRecords[idxRes0.n] === BE_ORDINAL.Not)
+            ) {
+                idxRes0.n = i;
+            }
+            if (fulfilled(idxRes0) && guessRecords[idxRes0.n] !== BE_ORDINAL.Not) {
+                return idxRes0;
+            }
+
+            // [Strategy of idxRes1]: if idxRes0 not satisfied (that is, no BE_ORDINAL.Not),
+            // find the first BE_ORDINAL.Might as the value dim,
+            // and then find a name dim with the priority:
+            // "other dim" > "the value dim itself".
+            // That is for backward compat: number-like (e.g., `'3'`, `'55'`) can be
+            // treated as number.
+            if (!isPureNumber) {
+                if (guessResult === BE_ORDINAL.Might && idxRes1.v == null && i !== potentialNameDimIndex) {
+                    idxRes1.v = i;
+                }
+                if (idxRes1.n == null || (idxRes1.n === idxRes1.v)) {
+                    idxRes1.n = i;
+                }
             }
         }
-        if (firstNotOrdinal != null) {
-            encode.value = firstNotOrdinal;
-            var nameDimIndex = completeResult.potentialNameDimIndex
-                || Math.max(firstNotOrdinal - 1, 0);
-            // By default, label use itemName in charts.
-            // So we dont set encodeLabel here.
-            encodeSeriesName.push(nameDimIndex);
-            encodeItemName.push(nameDimIndex);
-            // encodeTooltip.push(firstNotOrdinal);
+
+        function fulfilled(idxResult) {
+            return idxResult.v != null && idxResult.n != null;
         }
-    }
 
-    // encodeTooltip.length && (encode.tooltip = encodeTooltip);
-    // encodeLabel.length && (encode.label = encodeLabel);
-    encodeItemName.length && (encode.itemName = encodeItemName);
-    encodeSeriesName.length && (encode.seriesName = encodeSeriesName);
+        return fulfilled(idxRes0) ? idxRes0 : fulfilled(idxRes1) ? idxRes1 : null;
+    })();
+
+    if (idxResult) {
+        encode.value = idxResult.v;
+        // `potentialNameDimIndex` has highest priority.
+        var nameDimIndex = potentialNameDimIndex != null ? potentialNameDimIndex : idxResult.n;
+        // By default, label use itemName in charts.
+        // So we dont set encodeLabel here.
+        encode.itemName = [nameDimIndex];
+        encode.seriesName = [nameDimIndex];
+    }
 
     return encode;
 }
@@ -21272,7 +21500,7 @@ function getDatasetModel(seriesModel) {
  *
  * @param {module:echars/data/Source} source
  * @param {number} dimIndex
- * @return {boolean} Whether ordinal.
+ * @return {BE_ORDINAL} guess result.
  */
 function guessOrdinal(source, dimIndex) {
     return doGuessOrdinal(
@@ -21286,6 +21514,7 @@ function guessOrdinal(source, dimIndex) {
 }
 
 // dimIndex may be overflow source data.
+// return {BE_ORDINAL}
 function doGuessOrdinal(
     data, sourceFormat, seriesLayoutBy, dimensionsDefine, startIndex, dimIndex
 ) {
@@ -21294,15 +21523,26 @@ function doGuessOrdinal(
     var maxLoop = 5;
 
     if (isTypedArray(data)) {
-        return false;
+        return BE_ORDINAL.Not;
     }
 
     // When sourceType is 'objectRows' or 'keyedColumns', dimensionsDefine
     // always exists in source.
     var dimName;
+    var dimType;
     if (dimensionsDefine) {
-        dimName = dimensionsDefine[dimIndex];
-        dimName = isObject$1(dimName) ? dimName.name : dimName;
+        var dimDefItem = dimensionsDefine[dimIndex];
+        if (isObject$1(dimDefItem)) {
+            dimName = dimDefItem.name;
+            dimType = dimDefItem.type;
+        }
+        else if (isString(dimDefItem)) {
+            dimName = dimDefItem;
+        }
+    }
+
+    if (dimType != null) {
+        return dimType === 'ordinal' ? BE_ORDINAL.Must : BE_ORDINAL.Not;
     }
 
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
@@ -21325,7 +21565,7 @@ function doGuessOrdinal(
     }
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
         if (!dimName) {
-            return;
+            return BE_ORDINAL.Not;
         }
         for (var i = 0; i < data.length && i < maxLoop; i++) {
             var item = data[i];
@@ -21336,11 +21576,11 @@ function doGuessOrdinal(
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
         if (!dimName) {
-            return;
+            return BE_ORDINAL.Not;
         }
         var sample = data[dimName];
         if (!sample || isTypedArray(sample)) {
-            return false;
+            return BE_ORDINAL.Not;
         }
         for (var i = 0; i < sample.length && i < maxLoop; i++) {
             if ((result = detectValue(sample[i])) != null) {
@@ -21353,7 +21593,7 @@ function doGuessOrdinal(
             var item = data[i];
             var val = getDataItemValue(item);
             if (!isArray(val)) {
-                return false;
+                return BE_ORDINAL.Not;
             }
             if ((result = detectValue(val[dimIndex])) != null) {
                 return result;
@@ -21362,17 +21602,18 @@ function doGuessOrdinal(
     }
 
     function detectValue(val) {
+        var beStr = isString(val);
         // Consider usage convenience, '1', '2' will be treated as "number".
         // `isFinit('')` get `true`.
         if (val != null && isFinite(val) && val !== '') {
-            return false;
+            return beStr ? BE_ORDINAL.Might : BE_ORDINAL.Not;
         }
-        else if (isString(val) && val !== '-') {
-            return true;
+        else if (beStr && val !== '-') {
+            return BE_ORDINAL.Must;
         }
     }
 
-    return false;
+    return BE_ORDINAL.Not;
 }
 
 /*
@@ -24119,11 +24360,11 @@ var SeriesModel = ComponentModel.extend({
     defaultOption: null,
 
     /**
-     * Data provided for legend
-     * @type {Function}
+     * legend visual provider to the legend component
+     * @type {Object}
      */
     // PENDING
-    legendDataProvider: null,
+    legendVisualProvider: null,
 
     /**
      * Access path of color for visual
@@ -25247,25 +25488,30 @@ var seriesColor = {
     reset: function (seriesModel, ecModel) {
         var data = seriesModel.getData();
         var colorAccessPath = (seriesModel.visualColorAccessPath || 'itemStyle.color').split('.');
-        var color = seriesModel.get(colorAccessPath) // Set in itemStyle
-            || seriesModel.getColorFromPalette(
+        // Set in itemStyle
+        var color = seriesModel.get(colorAccessPath);
+        var colorCallback = (isFunction$1(color) && !(color instanceof Gradient))
+            ? color : null;
+        // Default color
+        if (!color || colorCallback) {
+            color = seriesModel.getColorFromPalette(
                 // TODO series count changed.
                 seriesModel.name, null, ecModel.getSeriesCount()
-            );  // Default color
+            );
+        }
 
-        // FIXME Set color function or use the platte color
         data.setVisual('color', color);
-        
+
         var borderColorAccessPath = (seriesModel.visualBorderColorAccessPath || 'itemStyle.borderColor').split('.');
         var borderColor = seriesModel.get(borderColorAccessPath);
         data.setVisual('borderColor', borderColor);
 
         // Only visible series has each data be visual encoded
         if (!ecModel.isSeriesFiltered(seriesModel)) {
-            if (typeof color === 'function' && !(color instanceof Gradient)) {
+            if (colorCallback) {
                 data.each(function (idx) {
                     data.setItemVisual(
-                        idx, 'color', color(seriesModel.getDataParams(idx))
+                        idx, 'color', colorCallback(seriesModel.getDataParams(idx))
                     );
                 });
             }
@@ -26324,6 +26570,9 @@ var theme = {
             },
             crossStyle: {
                 color: contrastColor
+            },
+            label: {
+                color: '#000'
             }
         }
     },
@@ -27294,10 +27543,10 @@ var isFunction = isFunction$1;
 var isObject = isObject$1;
 var parseClassType = ComponentModel.parseClassType;
 
-var version = '4.5.0';
+var version = '4.6.0';
 
 var dependencies = {
-    zrender: '4.1.2'
+    zrender: '4.2.0'
 };
 
 var TEST_FRAME_REMAIN_TIME = 1;
@@ -30004,6 +30253,138 @@ function mayLabelDimType(dimType) {
 * under the License.
 */
 
+/**
+ * @class
+ * @param {Object|DataDimensionInfo} [opt] All of the fields will be shallow copied.
+ */
+function DataDimensionInfo(opt) {
+    if (opt != null) {
+        extend(this, opt);
+    }
+
+    /**
+     * Dimension name.
+     * Mandatory.
+     * @type {string}
+     */
+    // this.name;
+
+    /**
+     * The origin name in dimsDef, see source helper.
+     * If displayName given, the tooltip will displayed vertically.
+     * Optional.
+     * @type {string}
+     */
+    // this.displayName;
+
+    /**
+     * Which coordSys dimension this dimension mapped to.
+     * A `coordDim` can be a "coordSysDim" that the coordSys required
+     * (for example, an item in `coordSysDims` of `model/referHelper#CoordSysInfo`),
+     * or an generated "extra coord name" if does not mapped to any "coordSysDim"
+     * (That is determined by whether `isExtraCoord` is `true`).
+     * Mandatory.
+     * @type {string}
+     */
+    // this.coordDim;
+
+    /**
+     * The index of this dimension in `series.encode[coordDim]`.
+     * Mandatory.
+     * @type {number}
+     */
+    // this.coordDimIndex;
+
+    /**
+     * Dimension type. The enumerable values are the key of
+     * `dataCtors` of `data/List`.
+     * Optional.
+     * @type {string}
+     */
+    // this.type;
+
+    /**
+     * This index of this dimension info in `data/List#_dimensionInfos`.
+     * Mandatory after added to `data/List`.
+     * @type {number}
+     */
+    // this.index;
+
+    /**
+     * The format of `otherDims` is:
+     * ```js
+     * {
+     *     tooltip: number optional,
+     *     label: number optional,
+     *     itemName: number optional,
+     *     seriesName: number optional,
+     * }
+     * ```
+     *
+     * A `series.encode` can specified these fields:
+     * ```js
+     * encode: {
+     *     // "3, 1, 5" is the index of data dimension.
+     *     tooltip: [3, 1, 5],
+     *     label: [0, 3],
+     *     ...
+     * }
+     * ```
+     * `otherDims` is the parse result of the `series.encode` above, like:
+     * ```js
+     * // Suppose the index of this data dimension is `3`.
+     * this.otherDims = {
+     *     // `3` is at the index `0` of the `encode.tooltip`
+     *     tooltip: 0,
+     *     // `3` is at the index `1` of the `encode.tooltip`
+     *     label: 1
+     * };
+     * ```
+     *
+     * This prop should never be `null`/`undefined` after initialized.
+     * @type {Object}
+     */
+    this.otherDims = {};
+
+    /**
+     * Be `true` if this dimension is not mapped to any "coordSysDim" that the
+     * "coordSys" required.
+     * Mandatory.
+     * @type {boolean}
+     */
+    // this.isExtraCoord;
+
+    /**
+     * @type {module:data/OrdinalMeta}
+     */
+    // this.ordinalMeta;
+
+    /**
+     * Whether to create inverted indices.
+     * @type {boolean}
+     */
+    // this.createInvertedIndices;
+}
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
 /* global Float64Array, Int32Array, Uint32Array, Uint16Array */
 
 /**
@@ -30081,13 +30462,9 @@ function transferProperties(target, source) {
  * @constructor
  * @alias module:echarts/data/List
  *
- * @param {Array.<string|Object>} dimensions
+ * @param {Array.<string|Object|module:data/DataDimensionInfo>} dimensions
  *      For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
  *      Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
- *      Spetial fields: {
- *          ordinalMeta: <module:echarts/data/OrdinalMeta>
- *          createInvertedIndices: <boolean>
- *      }
  * @param {module:echarts/model/Model} hostModel
  */
 var List = function (dimensions, hostModel) {
@@ -30103,7 +30480,10 @@ var List = function (dimensions, hostModel) {
         var dimensionInfo = dimensions[i];
 
         if (isString(dimensionInfo)) {
-            dimensionInfo = {name: dimensionInfo};
+            dimensionInfo = new DataDimensionInfo({name: dimensionInfo});
+        }
+        else if (!(dimensionInfo instanceof DataDimensionInfo)) {
+            dimensionInfo = new DataDimensionInfo(dimensionInfo);
         }
 
         var dimensionName = dimensionInfo.name;
@@ -31123,7 +31503,8 @@ listProto.indexOfRawIndex = function (rawIndex) {
  * @param {string} dim
  * @param {number} value
  * @param {number} [maxDistance=Infinity]
- * @return {Array.<number>} Considere multiple points has the same value.
+ * @return {Array.<number>} If and only if multiple indices has
+ *        the same value, they are put to the result.
  */
 listProto.indicesOfNearest = function (dim, value, maxDistance) {
     var storage = this._storage;
@@ -31138,23 +31519,35 @@ listProto.indicesOfNearest = function (dim, value, maxDistance) {
         maxDistance = Infinity;
     }
 
-    var minDist = Number.MAX_VALUE;
+    var minDist = Infinity;
     var minDiff = -1;
+    var nearestIndicesLen = 0;
+
+    // Check the test case of `test/ut/spec/data/List.js`.
     for (var i = 0, len = this.count(); i < len; i++) {
-        var diff = value - this.get(dim, i /*, stack */);
+        var diff = value - this.get(dim, i);
         var dist = Math.abs(diff);
-        if (diff <= maxDistance && dist <= minDist) {
-            // For the case of two data are same on xAxis, which has sequence data.
-            // Show the nearest index
-            // https://github.com/ecomfe/echarts/issues/2869
-            if (dist < minDist || (diff >= 0 && minDiff < 0)) {
+        if (dist <= maxDistance) {
+            // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`,
+            // we'd better not push both of them to `nearestIndices`, otherwise it is easy to
+            // get more than one item in `nearestIndices` (more specifically, in `tooltip`).
+            // So we chose the one that `diff >= 0` in this csae.
+            // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them
+            // should be push to `nearestIndices`.
+            if (dist < minDist
+                || (dist === minDist && diff >= 0 && minDiff < 0)
+            ) {
                 minDist = dist;
                 minDiff = diff;
-                nearestIndices.length = 0;
+                nearestIndicesLen = 0;
+            }
+            if (diff === minDiff) {
+                nearestIndices[nearestIndicesLen++] = i;
             }
-            nearestIndices.push(i);
         }
     }
+    nearestIndices.length = nearestIndicesLen;
+
     return nearestIndices;
 };
 
@@ -32028,8 +32421,12 @@ listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
 /**
  * @see {module:echarts/test/ut/spec/data/completeDimensions}
  *
- * Complete the dimensions array, by user defined `dimension` and `encode`,
- * and guessing from the data structure.
+ * This method builds the relationship between:
+ * + "what the coord sys or series requires (see `sysDims`)",
+ * + "what the user defines (in `encode` and `dimensions`, see `opt.dimsDef` and `opt.encodeDef`)"
+ * + "what the data source provids (see `source`)".
+ *
+ * Some guess strategy will be adapted if user does not define something.
  * If no 'value' dimension specified, the first no-named dimension will be
  * named as 'value'.
  *
@@ -32045,32 +32442,20 @@ listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
  * @param {Array.<Object|string>} [opt.dimsDef] option.series.dimensions User defined dimensions
  *      For example: ['asdf', {name, type}, ...].
  * @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1], tooltip: [1, 2], label: 3}
+ * @param {Function} [opt.encodeDefaulter] Called if no `opt.encodeDef` exists.
+ *      If not specified, auto find the next available data dim.
+ *      param source {module:data/Source}
+ *      param dimCount {number}
+ *      return {Object} encode Never be `null/undefined`.
  * @param {string} [opt.generateCoord] Generate coord dim with the given name.
- *                 If not specified, extra dim names will be:
- *                 'value', 'value0', 'value1', ...
+ *      If not specified, extra dim names will be:
+ *      'value', 'value0', 'value1', ...
  * @param {number} [opt.generateCoordCount] By default, the generated dim name is `generateCoord`.
- *                 If `generateCoordCount` specified, the generated dim names will be:
- *                 `generateCoord` + 0, `generateCoord` + 1, ...
- *                 can be Infinity, indicate that use all of the remain columns.
+ *      If `generateCoordCount` specified, the generated dim names will be:
+ *      `generateCoord` + 0, `generateCoord` + 1, ...
+ *      can be Infinity, indicate that use all of the remain columns.
  * @param {number} [opt.dimCount] If not specified, guess by the first data item.
- * @param {number} [opt.encodeDefaulter] If not specified, auto find the next available data dim.
- * @return {Array.<Object>} [{
- *      name: string mandatory,
- *      displayName: string, the origin name in dimsDef, see source helper.
- *                 If displayName given, the tooltip will displayed vertically.
- *      coordDim: string mandatory,
- *      coordDimIndex: number mandatory,
- *      type: string optional,
- *      otherDims: { never null/undefined
- *          tooltip: number optional,
- *          label: number optional,
- *          itemName: number optional,
- *          seriesName: number optional,
- *      },
- *      isExtraCoord: boolean true if coord is generated
- *          (not specified in encode and not series specified)
- *      other props ...
- * }]
+ * @return {Array.<module:data/DataDimensionInfo>}
  */
 function completeDimensions(sysDims, source, opt) {
     if (!Source.isInstance(source)) {
@@ -32080,7 +32465,6 @@ function completeDimensions(sysDims, source, opt) {
     opt = opt || {};
     sysDims = (sysDims || []).slice();
     var dimsDef = (opt.dimsDef || []).slice();
-    var encodeDef = createHashMap(opt.encodeDef);
     var dataDimNameMap = createHashMap();
     var coordDimNameMap = createHashMap();
     // var valueCandidate;
@@ -32094,7 +32478,7 @@ function completeDimensions(sysDims, source, opt) {
             {}, isObject$1(dimsDef[i]) ? dimsDef[i] : {name: dimsDef[i]}
         );
         var userDimName = dimDefItem.name;
-        var resultItem = result[i] = {otherDims: {}};
+        var resultItem = result[i] = new DataDimensionInfo();
         // Name will be applied later for avoiding duplication.
         if (userDimName != null && dataDimNameMap.get(userDimName) == null) {
             // Only if `series.dimensions` is defined in option
@@ -32107,6 +32491,12 @@ function completeDimensions(sysDims, source, opt) {
         dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName);
     }
 
+    var encodeDef = opt.encodeDef;
+    if (!encodeDef && opt.encodeDefaulter) {
+        encodeDef = opt.encodeDefaulter(source, dimCount);
+    }
+    encodeDef = createHashMap(encodeDef);
+
     // Set `coordDim` and `coordDimIndex` by `encodeDef` and normalize `encodeDef`.
     encodeDef.each(function (dataDims, coordDim) {
         dataDims = normalizeToArray(dataDims).slice();
@@ -32208,7 +32598,7 @@ function completeDimensions(sysDims, source, opt) {
 
     // Set dim `name` and other `coordDim` and other props.
     for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) {
-        var resultItem = result[resultDimIdx] = result[resultDimIdx] || {};
+        var resultItem = result[resultDimIdx] = result[resultDimIdx] || new DataDimensionInfo();
         var coordDim = resultItem.coordDim;
 
         if (coordDim == null) {
@@ -32227,7 +32617,28 @@ function completeDimensions(sysDims, source, opt) {
             dataDimNameMap
         ));
 
-        if (resultItem.type == null && guessOrdinal(source, resultDimIdx, resultItem.name)) {
+        if (resultItem.type == null
+            && (
+                guessOrdinal(source, resultDimIdx, resultItem.name) === BE_ORDINAL.Must
+                // Consider the case:
+                // {
+                //    dataset: {source: [
+                //        ['2001', 123],
+                //        ['2002', 456],
+                //        ...
+                //        ['The others', 987],
+                //    ]},
+                //    series: {type: 'pie'}
+                // }
+                // The first colum should better be treated as a "ordinal" although it
+                // might not able to be detected as an "ordinal" by `guessOrdinal`.
+                || (resultItem.isExtraCoord
+                    && (resultItem.otherDims.itemName != null
+                        || resultItem.otherDims.seriesName != null
+                    )
+                )
+            )
+        ) {
             resultItem.type = 'ordinal';
         }
     }
@@ -32305,6 +32716,7 @@ function genName(name, map$$1, fromZero) {
  * @param {string} [opt.generateCoordCount]
  * @param {Array.<string|Object>} [opt.dimensionsDefine=source.dimensionsDefine] Overwrite source define.
  * @param {Object|HashMap} [opt.encodeDefine=source.encodeDefine] Overwrite source define.
+ * @param {Function} [opt.encodeDefaulter] Make default encode if user not specified.
  * @return {Array.<Object>} dimensionsInfo
  */
 var createDimensions = function (source, opt) {
@@ -32313,6 +32725,7 @@ var createDimensions = function (source, opt) {
         dimsDef: opt.dimensionsDefine || source.dimensionsDefine,
         encodeDef: opt.encodeDefine || source.encodeDefine,
         dimCount: opt.dimensionsCount,
+        encodeDefaulter: opt.encodeDefaulter,
         generateCoord: opt.generateCoord,
         generateCoordCount: opt.generateCoordCount
     });
@@ -32338,6 +32751,201 @@ var createDimensions = function (source, opt) {
 */
 
 /**
+ * Helper for model references.
+ * There are many manners to refer axis/coordSys.
+ */
+
+// TODO
+// merge relevant logic to this file?
+// check: "modelHelper" of tooltip and "BrushTargetManager".
+
+/**
+ * @class
+ * For example:
+ * {
+ *     coordSysName: 'cartesian2d',
+ *     coordSysDims: ['x', 'y', ...],
+ *     axisMap: HashMap({
+ *         x: xAxisModel,
+ *         y: yAxisModel
+ *     }),
+ *     categoryAxisMap: HashMap({
+ *         x: xAxisModel,
+ *         y: undefined
+ *     }),
+ *     // The index of the first category axis in `coordSysDims`.
+ *     // `null/undefined` means no category axis exists.
+ *     firstCategoryDimIndex: 1,
+ *     // To replace user specified encode.
+ * }
+ */
+function CoordSysInfo(coordSysName) {
+    /**
+     * @type {string}
+     */
+    this.coordSysName = coordSysName;
+    /**
+     * @type {Array.<string>}
+     */
+    this.coordSysDims = [];
+    /**
+     * @type {module:zrender/core/util#HashMap}
+     */
+    this.axisMap = createHashMap();
+    /**
+     * @type {module:zrender/core/util#HashMap}
+     */
+    this.categoryAxisMap = createHashMap();
+    /**
+     * @type {number}
+     */
+    this.firstCategoryDimIndex = null;
+}
+
+/**
+ * @return {module:model/referHelper#CoordSysInfo}
+ */
+function getCoordSysInfoBySeries(seriesModel) {
+    var coordSysName = seriesModel.get('coordinateSystem');
+    var result = new CoordSysInfo(coordSysName);
+    var fetch = fetchers[coordSysName];
+    if (fetch) {
+        fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
+        return result;
+    }
+}
+
+var fetchers = {
+
+    cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var xAxisModel = seriesModel.getReferringComponents('xAxis')[0];
+        var yAxisModel = seriesModel.getReferringComponents('yAxis')[0];
+
+        if (__DEV__) {
+            if (!xAxisModel) {
+                throw new Error('xAxis "' + retrieve(
+                    seriesModel.get('xAxisIndex'),
+                    seriesModel.get('xAxisId'),
+                    0
+                ) + '" not found');
+            }
+            if (!yAxisModel) {
+                throw new Error('yAxis "' + retrieve(
+                    seriesModel.get('xAxisIndex'),
+                    seriesModel.get('yAxisId'),
+                    0
+                ) + '" not found');
+            }
+        }
+
+        result.coordSysDims = ['x', 'y'];
+        axisMap.set('x', xAxisModel);
+        axisMap.set('y', yAxisModel);
+
+        if (isCategory(xAxisModel)) {
+            categoryAxisMap.set('x', xAxisModel);
+            result.firstCategoryDimIndex = 0;
+        }
+        if (isCategory(yAxisModel)) {
+            categoryAxisMap.set('y', yAxisModel);
+            result.firstCategoryDimIndex == null & (result.firstCategoryDimIndex = 1);
+        }
+    },
+
+    singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0];
+
+        if (__DEV__) {
+            if (!singleAxisModel) {
+                throw new Error('singleAxis should be specified.');
+            }
+        }
+
+        result.coordSysDims = ['single'];
+        axisMap.set('single', singleAxisModel);
+
+        if (isCategory(singleAxisModel)) {
+            categoryAxisMap.set('single', singleAxisModel);
+            result.firstCategoryDimIndex = 0;
+        }
+    },
+
+    polar: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var polarModel = seriesModel.getReferringComponents('polar')[0];
+        var radiusAxisModel = polarModel.findAxisModel('radiusAxis');
+        var angleAxisModel = polarModel.findAxisModel('angleAxis');
+
+        if (__DEV__) {
+            if (!angleAxisModel) {
+                throw new Error('angleAxis option not found');
+            }
+            if (!radiusAxisModel) {
+                throw new Error('radiusAxis option not found');
+            }
+        }
+
+        result.coordSysDims = ['radius', 'angle'];
+        axisMap.set('radius', radiusAxisModel);
+        axisMap.set('angle', angleAxisModel);
+
+        if (isCategory(radiusAxisModel)) {
+            categoryAxisMap.set('radius', radiusAxisModel);
+            result.firstCategoryDimIndex = 0;
+        }
+        if (isCategory(angleAxisModel)) {
+            categoryAxisMap.set('angle', angleAxisModel);
+            result.firstCategoryDimIndex == null && (result.firstCategoryDimIndex = 1);
+        }
+    },
+
+    geo: function (seriesModel, result, axisMap, categoryAxisMap) {
+        result.coordSysDims = ['lng', 'lat'];
+    },
+
+    parallel: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var ecModel = seriesModel.ecModel;
+        var parallelModel = ecModel.getComponent(
+            'parallel', seriesModel.get('parallelIndex')
+        );
+        var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice();
+
+        each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) {
+            var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
+            var axisDim = coordSysDims[index];
+            axisMap.set(axisDim, axisModel);
+
+            if (isCategory(axisModel) && result.firstCategoryDimIndex == null) {
+                categoryAxisMap.set(axisDim, axisModel);
+                result.firstCategoryDimIndex = index;
+            }
+        });
+    }
+};
+
+function isCategory(axisModel) {
+    return axisModel.get('type') === 'category';
+}
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+/**
  * Note that it is too complicated to support 3d stack by value
  * (have to create two-dimension inverted index), so in 3d case
  * we just support that stacked by index.
@@ -32503,6 +33111,7 @@ function getStackedDimension(data, targetDim) {
  * @param {module:echarts/model/Series} seriesModel
  * @param {Object} [opt]
  * @param {string} [opt.generateCoord]
+ * @param {boolean} [opt.useEncodeDefaulter]
  */
 function createListFromArray(source, seriesModel, opt) {
     opt = opt || {};
@@ -32514,14 +33123,14 @@ function createListFromArray(source, seriesModel, opt) {
     var coordSysName = seriesModel.get('coordinateSystem');
     var registeredCoordSys = CoordinateSystemManager.get(coordSysName);
 
-    var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
+    var coordSysInfo = getCoordSysInfoBySeries(seriesModel);
 
     var coordSysDimDefs;
 
-    if (coordSysDefine) {
-        coordSysDimDefs = map(coordSysDefine.coordSysDims, function (dim) {
+    if (coordSysInfo) {
+        coordSysDimDefs = map(coordSysInfo.coordSysDims, function (dim) {
             var dimInfo = {name: dim};
-            var axisModel = coordSysDefine.axisMap.get(dim);
+            var axisModel = coordSysInfo.axisMap.get(dim);
             if (axisModel) {
                 var axisType = axisModel.get('type');
                 dimInfo.type = getDimensionTypeByAxis(axisType);
@@ -32542,14 +33151,17 @@ function createListFromArray(source, seriesModel, opt) {
 
     var dimInfoList = createDimensions(source, {
         coordDimensions: coordSysDimDefs,
-        generateCoord: opt.generateCoord
+        generateCoord: opt.generateCoord,
+        encodeDefaulter: opt.useEncodeDefaulter
+            ? curry(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel)
+            : null
     });
 
     var firstCategoryDimIndex;
     var hasNameEncode;
-    coordSysDefine && each$1(dimInfoList, function (dimInfo, dimIndex) {
+    coordSysInfo && each$1(dimInfoList, function (dimInfo, dimIndex) {
         var coordDim = dimInfo.coordDim;
-        var categoryAxisModel = coordSysDefine.categoryAxisMap.get(coordDim);
+        var categoryAxisModel = coordSysInfo.categoryAxisMap.get(coordDim);
         if (categoryAxisModel) {
             if (firstCategoryDimIndex == null) {
                 firstCategoryDimIndex = dimIndex;
@@ -33121,44 +33733,6 @@ function fixExtent(niceTickExtent, extent) {
     }
 }
 
-function intervalScaleGetTicks(interval, extent, niceTickExtent, intervalPrecision) {
-    var ticks = [];
-
-    // If interval is 0, return [];
-    if (!interval) {
-        return ticks;
-    }
-
-    // Consider this case: using dataZoom toolbox, zoom and zoom.
-    var safeLimit = 10000;
-
-    if (extent[0] < niceTickExtent[0]) {
-        ticks.push(extent[0]);
-    }
-    var tick = niceTickExtent[0];
-
-    while (tick <= niceTickExtent[1]) {
-        ticks.push(tick);
-        // Avoid rounding error
-        tick = roundNumber$1(tick + interval, intervalPrecision);
-        if (tick === ticks[ticks.length - 1]) {
-            // Consider out of safe float point, e.g.,
-            // -3711126.9907707 + 2e-10 === -3711126.9907707
-            break;
-        }
-        if (ticks.length > safeLimit) {
-            return [];
-        }
-    }
-    // Consider this case: the last item of ticks is smaller
-    // than niceTickExtent[1] and niceTickExtent[1] === extent[1].
-    if (extent[1] > (ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1])) {
-        ticks.push(extent[1]);
-    }
-
-    return ticks;
-}
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -33237,12 +33811,92 @@ var IntervalScale = Scale.extend({
     },
 
     /**
+     * @param {boolean} [expandToNicedExtent=false] If expand the ticks to niced extent.
      * @return {Array.<number>}
      */
-    getTicks: function () {
-        return intervalScaleGetTicks(
-            this._interval, this._extent, this._niceExtent, this._intervalPrecision
-        );
+    getTicks: function (expandToNicedExtent) {
+        var interval = this._interval;
+        var extent = this._extent;
+        var niceTickExtent = this._niceExtent;
+        var intervalPrecision = this._intervalPrecision;
+
+        var ticks = [];
+        // If interval is 0, return [];
+        if (!interval) {
+            return ticks;
+        }
+
+        // Consider this case: using dataZoom toolbox, zoom and zoom.
+        var safeLimit = 10000;
+
+        if (extent[0] < niceTickExtent[0]) {
+            if (expandToNicedExtent) {
+                ticks.push(roundNumber(niceTickExtent[0] - interval));
+            }
+            else {
+                ticks.push(extent[0]);
+            }
+        }
+        var tick = niceTickExtent[0];
+
+        while (tick <= niceTickExtent[1]) {
+            ticks.push(tick);
+            // Avoid rounding error
+            tick = roundNumber(tick + interval, intervalPrecision);
+            if (tick === ticks[ticks.length - 1]) {
+                // Consider out of safe float point, e.g.,
+                // -3711126.9907707 + 2e-10 === -3711126.9907707
+                break;
+            }
+            if (ticks.length > safeLimit) {
+                return [];
+            }
+        }
+        // Consider this case: the last item of ticks is smaller
+        // than niceTickExtent[1] and niceTickExtent[1] === extent[1].
+        var lastNiceTick = ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1];
+        if (extent[1] > lastNiceTick) {
+            if (expandToNicedExtent) {
+                ticks.push(lastNiceTick + interval);
+            }
+            else {
+                ticks.push(extent[1]);
+            }
+        }
+
+        return ticks;
+    },
+
+    /**
+     * @param {number} [splitNumber=5]
+     * @return {Array.<Array.<number>>}
+     */
+    getMinorTicks: function (splitNumber) {
+        var ticks = this.getTicks(true);
+        var minorTicks = [];
+        var extent = this.getExtent();
+
+        for (var i = 1; i < ticks.length; i++) {
+            var nextTick = ticks[i];
+            var prevTick = ticks[i - 1];
+            var count = 0;
+            var minorTicksGroup = [];
+            var interval = nextTick - prevTick;
+            var minorInterval = interval / splitNumber;
+
+            while (count < splitNumber - 1) {
+                var minorTick = round$1(prevTick + (count + 1) * minorInterval);
+
+                // For the first and last interval. The count may be less than splitNumber.
+                if (minorTick > extent[0] && minorTick < extent[1]) {
+                    minorTicksGroup.push(minorTick);
+                }
+                count++;
+            }
+            minorTicks.push(minorTicksGroup);
+        }
+
+        return minorTicks;
     },
 
     /**
@@ -33581,7 +34235,6 @@ function doCalBarWidthAndOffset(seriesInfoList) {
         // only the attributes set on the last series will work.
         // Do not change this fact unless there will be a break change.
 
-        // TODO
         var barWidth = seriesInfo.barWidth;
         if (barWidth && !stacks[stackId].width) {
             // See #6312, do not restrict width.
@@ -33621,6 +34274,7 @@ function doCalBarWidthAndOffset(seriesInfoList) {
         each$1(stacks, function (column) {
             var maxWidth = column.maxWidth;
             var minWidth = column.minWidth;
+
             if (!column.width) {
                 var finalWidth = autoWidth;
                 if (maxWidth && maxWidth < finalWidth) {
@@ -33636,7 +34290,7 @@ function doCalBarWidthAndOffset(seriesInfoList) {
                 }
                 if (finalWidth !== autoWidth) {
                     column.width = finalWidth;
-                    remainedWidth -= finalWidth;
+                    remainedWidth -= finalWidth + barGapPercent * finalWidth;
                     autoWidthCount--;
                 }
             }
@@ -33653,7 +34307,7 @@ function doCalBarWidthAndOffset(seriesInfoList) {
                     finalWidth = Math.max(finalWidth, minWidth);
                 }
                 column.width = finalWidth;
-                remainedWidth -= finalWidth;
+                remainedWidth -= finalWidth + barGapPercent * finalWidth;
                 autoWidthCount--;
             }
         });
@@ -33661,8 +34315,10 @@ function doCalBarWidthAndOffset(seriesInfoList) {
         // Recalculate width again
         autoWidth = (remainedWidth - categoryGap)
             / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
+
         autoWidth = Math.max(autoWidth, 0);
 
+
         var widthSum = 0;
         var lastColumn;
         each$1(stacks, function (column, idx) {
@@ -33801,6 +34457,7 @@ function layout(seriesType, ecModel) {
                 }
                 stacked && (lastStackCoords[stackId][baseValue][sign] += height);
             }
+
             data.setItemLayout(idx, {
                 x: x,
                 y: y,
@@ -34161,14 +34818,15 @@ var LogScale = Scale.extend({
     },
 
     /**
+     * @param {boolean} [expandToNicedExtent=false] If expand the ticks to niced extent.
      * @return {Array.<number>}
      */
-    getTicks: function () {
+    getTicks: function (expandToNicedExtent) {
         var originalScale = this._originalScale;
         var extent = this._extent;
         var originalExtent = originalScale.getExtent();
 
-        return map(intervalScaleProto$1.getTicks.call(this), function (val) {
+        return map(intervalScaleProto$1.getTicks.call(this, expandToNicedExtent), function (val) {
             var powVal = round$1(mathPow$1(this.base, val));
 
             // Fix #4158
@@ -34184,6 +34842,12 @@ var LogScale = Scale.extend({
     },
 
     /**
+     * @param {number} splitNumber
+     * @return {Array.<Array.<number>>}
+     */
+    getMinorTicks: intervalScaleProto$1.getMinorTicks,
+
+    /**
      * @param {number} val
      * @return {string}
      */
@@ -36046,7 +36710,7 @@ Axis.prototype = {
      * @return {boolean}
      */
     containData: function (data) {
-        return this.contain(this.dataToCoord(data));
+        return this.scale.contain(data);
     },
 
     /**
@@ -36134,7 +36798,7 @@ Axis.prototype = {
      * `axis.getTicksCoords` considers `onBand`, which is used by
      * `boundaryGap:true` of category axis and splitLine and splitArea.
      * @param {Object} [opt]
-     * @param {number} [opt.tickModel=axis.model.getModel('axisTick')]
+     * @param {Model} [opt.tickModel=axis.model.getModel('axisTick')]
      * @param {boolean} [opt.clamp] If `true`, the first and the last
      *        tick must be at the axis end points. Otherwise, clip ticks
      *        that outside the axis extent.
@@ -36167,6 +36831,33 @@ Axis.prototype = {
     },
 
     /**
+     * @return {Array.<Array.<Object>>} [{ coord: ..., tickValue: ...}]
+     */
+    getMinorTicksCoords: function () {
+        if (this.scale.type === 'ordinal') {
+            // Category axis doesn't support minor ticks
+            return [];
+        }
+
+        var minorTickModel = this.model.getModel('minorTick');
+        var splitNumber = minorTickModel.get('splitNumber');
+        // Protection.
+        if (!(splitNumber > 0 && splitNumber < 100)) {
+            splitNumber = 5;
+        }
+        var minorTicks = this.scale.getMinorTicks(splitNumber);
+        var minorTicksCoords = map(minorTicks, function (minorTicksGroup) {
+            return map(minorTicksGroup, function (minorTick) {
+                return {
+                    coord: this.dataToCoord(minorTick),
+                    tickValue: minorTick
+                };
+            }, this);
+        }, this);
+        return minorTicksCoords;
+    },
+
+    /**
      * @return {Array.<Object>} [{
      *     formattedLabel: string,
      *     rawLabel: axis.scale.getLabel(tickValue)
@@ -36408,7 +37099,7 @@ SeriesModel.extend({
                 throw new Error('Line not support coordinateSystem besides cartesian and polar');
             }
         }
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     defaultOption: {
@@ -38252,7 +38943,7 @@ Chart.extend({
         // FIXME step not support polar
         var step = !isCoordSysPolar && seriesModel.get('step');
         var clipShapeForSymbol;
-        if (coordSys && coordSys.getArea) {
+        if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) {
             clipShapeForSymbol = coordSys.getArea();
             // Avoid float number rounding error for symbol on the edge of axis extent.
             // See #7913 and `test/dataZoom-clip.html`.
@@ -38267,6 +38958,7 @@ Chart.extend({
                 clipShapeForSymbol.r1 += 0.5;
             }
         }
+        this._clipShapeForSymbol = clipShapeForSymbol;
         // Initialization animation or coordinate system changed
         if (
             !(polyline && prevCoordSys.type === coordSys.type && step === this._step)
@@ -38419,6 +39111,10 @@ Chart.extend({
                     // Null data
                     return;
                 }
+                // fix #11360: should't draw symbol outside clipShapeForSymbol
+                if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(pt[0], pt[1])) {
+                    return;
+                }
                 symbol = new SymbolClz$1(data, dataIndex);
                 symbol.position = pt;
                 symbol.setZ(
@@ -39474,7 +40170,7 @@ axisDefault.valueAxis = merge({
     // scale: false,
 
     // AxisTick and axisLabel and splitLine are caculated based on splitNumber.
-    splitNumber: 5
+    splitNumber: 5,
 
     // Interval specifies the span of the ticks is mandatorily.
     // interval: null
@@ -39485,6 +40181,30 @@ axisDefault.valueAxis = merge({
     // Specify max interval when auto calculate tick interval.
     // maxInterval: null
 
+    minorTick: {
+        // Minor tick, not available for cateogry axis.
+        show: false,
+        // Split number of minor ticks. The value should be in range of (0, 100)
+        splitNumber: 5,
+        // Lenght of minor tick
+        length: 3,
+
+        // Same inside with axisTick
+
+        // Line style
+        lineStyle: {
+            // Default to be same with axisTick
+        }
+    },
+
+    minorSplitLine: {
+        show: false,
+
+        lineStyle: {
+            color: '#eee',
+            width: 1
+        }
+    }
 }, defaultOption);
 
 axisDefault.timeAxis = defaults({
@@ -40601,10 +41321,12 @@ var builders = {
         var axisModel = this.axisModel;
         var opt = this.opt;
 
-        var tickEls = buildAxisTick(this, axisModel, opt);
+        var ticksEls = buildAxisMajorTicks(this, axisModel, opt);
         var labelEls = buildAxisLabel(this, axisModel, opt);
 
-        fixMinMaxLabelShow(axisModel, labelEls, tickEls);
+        fixMinMaxLabelShow(axisModel, labelEls, ticksEls);
+
+        buildAxisMinorTicks(this, axisModel, opt);
     },
 
     /**
@@ -40923,42 +41645,27 @@ function isNameLocationCenter(nameLocation) {
     return nameLocation === 'middle' || nameLocation === 'center';
 }
 
-function buildAxisTick(axisBuilder, axisModel, opt) {
-    var axis = axisModel.axis;
-
-    if (!axisModel.get('axisTick.show') || axis.scale.isBlank()) {
-        return;
-    }
-
-    var tickModel = axisModel.getModel('axisTick');
-
-    var lineStyleModel = tickModel.getModel('lineStyle');
-    var tickLen = tickModel.get('length');
-
-    var ticksCoords = axis.getTicksCoords();
 
+function createTicks(ticksCoords, tickTransform, tickEndCoord, tickLineStyle, aniid) {
+    var tickEls = [];
     var pt1 = [];
     var pt2 = [];
-    var matrix = axisBuilder._transform;
-
-    var tickEls = [];
-
     for (var i = 0; i < ticksCoords.length; i++) {
         var tickCoord = ticksCoords[i].coord;
 
         pt1[0] = tickCoord;
         pt1[1] = 0;
         pt2[0] = tickCoord;
-        pt2[1] = opt.tickDirection * tickLen;
+        pt2[1] = tickEndCoord;
 
-        if (matrix) {
-            applyTransform(pt1, pt1, matrix);
-            applyTransform(pt2, pt2, matrix);
+        if (tickTransform) {
+            applyTransform(pt1, pt1, tickTransform);
+            applyTransform(pt2, pt2, tickTransform);
         }
         // Tick line, Not use group transform to have better line draw
         var tickEl = new Line({
             // Id for animation
-            anid: 'tick_' + ticksCoords[i].tickValue,
+            anid: aniid + '_' + ticksCoords[i].tickValue,
             subPixelOptimize: true,
             shape: {
                 x1: pt1[0],
@@ -40966,22 +41673,80 @@ function buildAxisTick(axisBuilder, axisModel, opt) {
                 x2: pt2[0],
                 y2: pt2[1]
             },
-            style: defaults(
-                lineStyleModel.getLineStyle(),
-                {
-                    stroke: axisModel.get('axisLine.lineStyle.color')
-                }
-            ),
+            style: tickLineStyle,
             z2: 2,
             silent: true
         });
-        axisBuilder.group.add(tickEl);
         tickEls.push(tickEl);
     }
-
     return tickEls;
 }
 
+function buildAxisMajorTicks(axisBuilder, axisModel, opt) {
+    var axis = axisModel.axis;
+
+    var tickModel = axisModel.getModel('axisTick');
+
+    if (!tickModel.get('show') || axis.scale.isBlank()) {
+        return;
+    }
+
+    var lineStyleModel = tickModel.getModel('lineStyle');
+    var tickEndCoord = opt.tickDirection * tickModel.get('length');
+
+    var ticksCoords = axis.getTicksCoords();
+
+    var ticksEls = createTicks(ticksCoords, axisBuilder._transform, tickEndCoord, defaults(
+        lineStyleModel.getLineStyle(),
+        {
+            stroke: axisModel.get('axisLine.lineStyle.color')
+        }
+    ), 'ticks');
+
+    for (var i = 0; i < ticksEls.length; i++) {
+        axisBuilder.group.add(ticksEls[i]);
+    }
+
+    return ticksEls;
+}
+
+function buildAxisMinorTicks(axisBuilder, axisModel, opt) {
+    var axis = axisModel.axis;
+
+    var minorTickModel = axisModel.getModel('minorTick');
+
+    if (!minorTickModel.get('show') || axis.scale.isBlank()) {
+        return;
+    }
+
+    var minorTicksCoords = axis.getMinorTicksCoords();
+    if (!minorTicksCoords.length) {
+        return;
+    }
+
+    var lineStyleModel = minorTickModel.getModel('lineStyle');
+    var tickEndCoord = opt.tickDirection * minorTickModel.get('length');
+
+    var minorTickLineStyle = defaults(
+        lineStyleModel.getLineStyle(),
+        defaults(
+            axisModel.getModel('axisTick').getLineStyle(),
+            {
+                stroke: axisModel.get('axisLine.lineStyle.color')
+            }
+        )
+    );
+
+    for (var i = 0; i < minorTicksCoords.length; i++) {
+        var minorTicksEls = createTicks(
+            minorTicksCoords[i], axisBuilder._transform, tickEndCoord, minorTickLineStyle, 'minorticks_' + i
+        );
+        for (var k = 0; k < minorTicksEls.length; k++) {
+            axisBuilder.group.add(minorTicksEls[k]);
+        }
+    }
+}
+
 function buildAxisLabel(axisBuilder, axisModel, opt) {
     var axis = axisModel.axis;
     var show = retrieve(opt.axisLabelShow, axisModel.get('axisLabel.show'));
@@ -41650,17 +42415,9 @@ var axisBuilderAttrs = [
     'axisLine', 'axisTickLabel', 'axisName'
 ];
 var selfBuilderAttrs = [
-    'splitArea', 'splitLine'
+    'splitArea', 'splitLine', 'minorSplitLine'
 ];
 
-// function getAlignWithLabel(model, axisModel) {
-//     var alignWithLabel = model.get('alignWithLabel');
-//     if (alignWithLabel === 'auto') {
-//         alignWithLabel = axisModel.get('axisTick.alignWithLabel');
-//     }
-//     return alignWithLabel;
-// }
-
 var CartesianAxisView = AxisView.extend({
 
     type: 'cartesianAxis',
@@ -41738,8 +42495,6 @@ var CartesianAxisView = AxisView.extend({
         var p1 = [];
         var p2 = [];
 
-        // Simple optimization
-        // Batching the lines if color are the same
         var lineStyle = lineStyleModel.getLineStyle();
         for (var i = 0; i < ticksCoords.length; i++) {
             var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord);
@@ -41781,6 +42536,63 @@ var CartesianAxisView = AxisView.extend({
      * @param {module:echarts/coord/cartesian/GridModel} gridModel
      * @private
      */
+    _minorSplitLine: function (axisModel, gridModel) {
+        var axis = axisModel.axis;
+
+        var minorSplitLineModel = axisModel.getModel('minorSplitLine');
+        var lineStyleModel = minorSplitLineModel.getModel('lineStyle');
+
+        var gridRect = gridModel.coordinateSystem.getRect();
+        var isHorizontal = axis.isHorizontal();
+
+        var minorTicksCoords = axis.getMinorTicksCoords();
+        if (!minorTicksCoords.length) {
+            return;
+        }
+        var p1 = [];
+        var p2 = [];
+
+        var lineStyle = lineStyleModel.getLineStyle();
+
+
+        for (var i = 0; i < minorTicksCoords.length; i++) {
+            for (var k = 0; k < minorTicksCoords[i].length; k++) {
+                var tickCoord = axis.toGlobalCoord(minorTicksCoords[i][k].coord);
+
+                if (isHorizontal) {
+                    p1[0] = tickCoord;
+                    p1[1] = gridRect.y;
+                    p2[0] = tickCoord;
+                    p2[1] = gridRect.y + gridRect.height;
+                }
+                else {
+                    p1[0] = gridRect.x;
+                    p1[1] = tickCoord;
+                    p2[0] = gridRect.x + gridRect.width;
+                    p2[1] = tickCoord;
+                }
+
+                this._axisGroup.add(new Line({
+                    anid: 'minor_line_' + minorTicksCoords[i][k].tickValue,
+                    subPixelOptimize: true,
+                    shape: {
+                        x1: p1[0],
+                        y1: p1[1],
+                        x2: p2[0],
+                        y2: p2[1]
+                    },
+                    style: lineStyle,
+                    silent: true
+                }));
+            }
+        }
+    },
+
+    /**
+     * @param {module:echarts/coord/cartesian/AxisModel} axisModel
+     * @param {module:echarts/coord/cartesian/GridModel} gridModel
+     * @private
+     */
     _splitArea: function (axisModel, gridModel) {
         var axis = axisModel.axis;
 
@@ -41996,7 +42808,7 @@ var BaseBarSeries = SeriesModel.extend({
     type: 'series.__base_bar__',
 
     getInitialData: function (option, ecModel) {
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     getMarkerPosition: function (value) {
@@ -42700,11 +43512,18 @@ var getLayout = {
     }
 };
 
+function isZeroOnPolar(layout) {
+    return layout.startAngle != null
+        && layout.endAngle != null
+        && layout.startAngle === layout.endAngle;
+}
+
 function updateStyle(
     el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal, isPolar
 ) {
     var color = data.getItemVisual(dataIndex, 'color');
     var opacity = data.getItemVisual(dataIndex, 'opacity');
+    var stroke = data.getVisual('borderColor');
     var itemStyleModel = itemModel.getModel('itemStyle');
     var hoverStyle = itemModel.getModel('emphasis.itemStyle').getBarItemStyle();
 
@@ -42714,7 +43533,8 @@ function updateStyle(
 
     el.useStyle(defaults(
         {
-            fill: color,
+            stroke: isZeroOnPolar(layout) ? 'none' : stroke,
+            fill: isZeroOnPolar(layout) ? 'none' : color,
             opacity: opacity
         },
         itemStyleModel.getBarItemStyle()
@@ -42733,7 +43553,9 @@ function updateStyle(
             seriesModel, dataIndex, labelPositionOutside
         );
     }
-
+    if (isZeroOnPolar(layout)) {
+        hoverStyle.fill = hoverStyle.stroke = 'none';
+    }
     setHoverStyle(el, hoverStyle);
 }
 
@@ -43054,6 +43876,60 @@ var dataSelectableMixin = {
 * under the License.
 */
 
+
+/**
+ * LegendVisualProvider is an bridge that pick encoded color from data and
+ * provide to the legend component.
+ * @param {Function} getDataWithEncodedVisual Function to get data after filtered. It stores all the encoding info
+ * @param {Function} getRawData Function to get raw data before filtered.
+ */
+function LegendVisualProvider(getDataWithEncodedVisual, getRawData) {
+    this.getAllNames = function () {
+        var rawData = getRawData();
+        // We find the name from the raw data. In case it's filtered by the legend component.
+        // Normally, the name can be found in rawData, but can't be found in filtered data will display as gray.
+        return rawData.mapArray(rawData.getName);
+    };
+
+    this.containName = function (name) {
+        var rawData = getRawData();
+        return rawData.indexOfName(name) >= 0;
+    };
+
+    this.indexOfName = function (name) {
+        // Only get data when necessary.
+        // Because LegendVisualProvider constructor may be new in the stage that data is not prepared yet.
+        // Invoking Series#getData immediately will throw an error.
+        var dataWithEncodedVisual = getDataWithEncodedVisual();
+        return dataWithEncodedVisual.indexOfName(name);
+    };
+
+    this.getItemVisual = function (dataIndex, key) {
+        // Get encoded visual properties from final filtered data.
+        var dataWithEncodedVisual = getDataWithEncodedVisual();
+        return dataWithEncodedVisual.getItemVisual(dataIndex, key);
+    };
+}
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
 var PieSeries = extendSeriesModel({
 
     type: 'series.pie',
@@ -43064,9 +43940,9 @@ var PieSeries = extendSeriesModel({
 
         // Enable legend selection for each data item
         // Use a function instead of direct access because data reference may changed
-        this.legendDataProvider = function () {
-            return this.getRawData();
-        };
+        this.legendVisualProvider = new LegendVisualProvider(
+            bind(this.getData, this), bind(this.getRawData, this)
+        );
 
         this.updateSelectedMap(this._createSelectableList());
 
@@ -43081,7 +43957,10 @@ var PieSeries = extendSeriesModel({
     },
 
     getInitialData: function (option, ecModel) {
-        return createListSimply(this, ['value']);
+        return createListSimply(this, {
+            coordDimensions: ['value'],
+            encodeDefaulter: curry(makeSeriesEncodeForNameBased, this)
+        });
     },
 
     _createSelectableList: function () {
@@ -43170,12 +44049,28 @@ var PieSeries = extendSeriesModel({
 
         // cursor: null,
 
+        left: 0,
+        top: 0,
+        right: 0,
+        bottom: 0,
+        width: null,
+        height: null,
+
         label: {
             // If rotate around circle
             rotate: false,
             show: true,
             // 'outer', 'inside', 'center'
-            position: 'outer'
+            position: 'outer',
+            // 'none', 'labelLine', 'edge'. Works only when position is 'outer'
+            alignTo: 'none',
+            // Closest distance between label and chart edge.
+            // Works only position is 'outer' and alignTo is 'edge'.
+            margin: '25%',
+            // Works only position is 'outer' and alignTo is not 'edge'.
+            bleedMargin: 10,
+            // Distance between text and label line.
+            distanceToLabelLine: 5
             // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
             // 默认使用全局文本样式,详见TEXTSTYLE
             // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数
@@ -43481,7 +44376,7 @@ piePieceProto._updateLabel = function (data, idx, withAnimation) {
         {
             labelFetcher: data.hostModel,
             labelDataIndex: idx,
-            defaultText: data.getName(idx),
+            defaultText: labelLayout.text,
             autoColor: visualColor,
             useInsideStyle: !!labelLayout.inside
         },
@@ -43777,33 +44672,20 @@ var dataColor = function (seriesType) {
                             dataAll.getName(rawIdx) || (rawIdx + ''), seriesModel.__paletteScope,
                             dataAll.count()
                         );
-                    // Legend may use the visual info in data before processed
-                    dataAll.setItemVisual(rawIdx, 'color', color);
-
                     // Data is not filtered
                     if (filteredIdx != null) {
                         data.setItemVisual(filteredIdx, 'color', color);
                     }
                 }
-                else {
-                    // Set data all color for legend
-                    dataAll.setItemVisual(rawIdx, 'color', singleDataColor);
-                }
 
                 if (!singleDataBorderColor) {
                     var borderColor = itemModel.get('itemStyle.borderColor');
-                    // Legend may use the visual info in data before processed
-                    dataAll.setItemVisual(rawIdx, 'borderColor', borderColor);
 
                     // Data is not filtered
                     if (filteredIdx != null) {
                         data.setItemVisual(filteredIdx, 'borderColor', borderColor);
                     }
                 }
-                else {
-                    // Set data all borderColor for legend
-                    dataAll.setItemVisual(rawIdx, 'borderColor', singleDataBorderColor);
-                }
             });
         }
     };
@@ -43832,13 +44714,17 @@ var dataColor = function (seriesType) {
 
 var RADIAN$1 = Math.PI / 180;
 
-function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
+function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) {
     list.sort(function (a, b) {
         return a.y - b.y;
     });
 
     function shiftDown(start, end, delta, dir) {
         for (var j = start; j < end; j++) {
+            if (list[j].y + delta > viewTop + viewHeight) {
+                break;
+            }
+
             list[j].y += delta;
             if (j > start
                 && j + 1 < end
@@ -43854,6 +44740,10 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
 
     function shiftUp(end, delta) {
         for (var j = end; j >= 0; j--) {
+            if (list[j].y - delta < viewTop) {
+                break;
+            }
+
             list[j].y -= delta;
             if (j > 0
                 && list[j].y > list[j - 1].y + list[j - 1].height
@@ -43873,6 +44763,10 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
                 : 0;                    // up
 
         for (var i = 0, l = list.length; i < l; i++) {
+            if (list[i].labelAlignTo !== 'none') {
+                continue;
+            }
+
             var deltaY = Math.abs(list[i].y - cy);
             var length = list[i].len;
             var length2 = list[i].len2;
@@ -43902,6 +44796,12 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
     var upList = [];
     var downList = [];
     for (var i = 0; i < len; i++) {
+        if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
+            var dx = list[i].x - farthestX;
+            list[i].linePoints[1][0] += dx;
+            list[i].x = farthestX;
+        }
+
         delta = list[i].y - lastY;
         if (delta < 0) {
             shiftDown(i, len, -delta, dir);
@@ -43923,39 +44823,85 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
     changeX(downList, true, cx, cy, r, dir);
 }
 
-function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight) {
+function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) {
     var leftList = [];
     var rightList = [];
+    var leftmostX = Number.MAX_VALUE;
+    var rightmostX = -Number.MAX_VALUE;
     for (var i = 0; i < labelLayoutList.length; i++) {
         if (isPositionCenter(labelLayoutList[i])) {
             continue;
         }
         if (labelLayoutList[i].x < cx) {
+            leftmostX = Math.min(leftmostX, labelLayoutList[i].x);
             leftList.push(labelLayoutList[i]);
         }
         else {
+            rightmostX = Math.max(rightmostX, labelLayoutList[i].x);
             rightList.push(labelLayoutList[i]);
         }
     }
 
-    adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight);
-    adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight);
+    adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX);
+    adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX);
 
     for (var i = 0; i < labelLayoutList.length; i++) {
-        if (isPositionCenter(labelLayoutList[i])) {
+        var layout = labelLayoutList[i];
+        if (isPositionCenter(layout)) {
             continue;
         }
-        var linePoints = labelLayoutList[i].linePoints;
+
+        var linePoints = layout.linePoints;
         if (linePoints) {
+            var isAlignToEdge = layout.labelAlignTo === 'edge';
+
+            var realTextWidth = layout.textRect.width;
+            var targetTextWidth;
+            if (isAlignToEdge) {
+                if (layout.x < cx) {
+                    targetTextWidth = linePoints[2][0] - layout.labelDistance
+                            - viewLeft - layout.labelMargin;
+                }
+                else {
+                    targetTextWidth = viewLeft + viewWidth - layout.labelMargin
+                            - linePoints[2][0] - layout.labelDistance;
+                }
+            }
+            else {
+                if (layout.x < cx) {
+                    targetTextWidth = layout.x - viewLeft - layout.bleedMargin;
+                }
+                else {
+                    targetTextWidth = viewLeft + viewWidth - layout.x - layout.bleedMargin;
+                }
+            }
+            if (targetTextWidth < layout.textRect.width) {
+                layout.text = truncateText(layout.text, targetTextWidth, layout.font);
+                if (layout.labelAlignTo === 'edge') {
+                    realTextWidth = getWidth(layout.text, layout.font);
+                }
+            }
+
             var dist = linePoints[1][0] - linePoints[2][0];
-            if (labelLayoutList[i].x < cx) {
-                linePoints[2][0] = labelLayoutList[i].x + 3;
+            if (isAlignToEdge) {
+                if (layout.x < cx) {
+                    linePoints[2][0] = viewLeft + layout.labelMargin + realTextWidth + layout.labelDistance;
+                }
+                else {
+                    linePoints[2][0] = viewLeft + viewWidth - layout.labelMargin
+                            - realTextWidth - layout.labelDistance;
+                }
             }
             else {
-                linePoints[2][0] = labelLayoutList[i].x - 3;
+                if (layout.x < cx) {
+                    linePoints[2][0] = layout.x + layout.labelDistance;
+                }
+                else {
+                    linePoints[2][0] = layout.x - layout.labelDistance;
+                }
+                linePoints[1][0] = linePoints[2][0] + dist;
             }
-            linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y;
-            linePoints[1][0] = linePoints[2][0] + dist;
+            linePoints[1][1] = linePoints[2][1] = layout.y;
         }
     }
 }
@@ -43965,7 +44911,7 @@ function isPositionCenter(layout) {
     return layout.position === 'center';
 }
 
-var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
+var labelLayout = function (seriesModel, r, viewWidth, viewHeight, viewLeft, viewTop) {
     var data = seriesModel.getData();
     var labelLayoutList = [];
     var cx;
@@ -43980,10 +44926,17 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
         var labelModel = itemModel.getModel('label');
         // Use position in normal or emphasis
         var labelPosition = labelModel.get('position') || itemModel.get('emphasis.label.position');
+        var labelDistance = labelModel.get('distanceToLabelLine');
+        var labelAlignTo = labelModel.get('alignTo');
+        var labelMargin = parsePercent$1(labelModel.get('margin'), viewWidth);
+        var bleedMargin = labelModel.get('bleedMargin');
+        var font = labelModel.getFont();
 
         var labelLineModel = itemModel.getModel('labelLine');
         var labelLineLen = labelLineModel.get('length');
+        labelLineLen = parsePercent$1(labelLineLen, viewWidth);
         var labelLineLen2 = labelLineModel.get('length2');
+        labelLineLen2 = parsePercent$1(labelLineLen2, viewWidth);
 
         if (layout.angle < minShowLabelRadian) {
             return;
@@ -44001,6 +44954,12 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
         cx = layout.cx;
         cy = layout.cy;
 
+        var text = seriesModel.getFormattedLabel(idx, 'normal')
+                || data.getName(idx);
+        var textRect = getBoundingRect(
+            text, font, textAlign, 'top'
+        );
+
         var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
         if (labelPosition === 'center') {
             textX = layout.cx;
@@ -44021,14 +44980,25 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
                 var x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2);
                 var y3 = y2;
 
-                textX = x3 + (dx < 0 ? -5 : 5);
+                if (labelAlignTo === 'edge') {
+                    // Adjust textX because text align of edge is opposite
+                    textX = dx < 0
+                        ? viewLeft + labelMargin
+                        : viewLeft + viewWidth - labelMargin;
+                }
+                else {
+                    textX = x3 + (dx < 0 ? -labelDistance : labelDistance);
+                }
                 textY = y3;
                 linePoints = [[x1, y1], [x2, y2], [x3, y3]];
             }
 
-            textAlign = isLabelInside ? 'center' : (dx > 0 ? 'left' : 'right');
+            textAlign = isLabelInside
+                ? 'center'
+                : (labelAlignTo === 'edge'
+                    ? (dx > 0 ? 'right' : 'left')
+                    : (dx > 0 ? 'left' : 'right'));
         }
-        var font = labelModel.getFont();
 
         var labelRotate;
         var rotate = labelModel.get('rotate');
@@ -44040,11 +45010,7 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
                 ? (dx < 0 ? -midAngle + Math.PI : -midAngle)
                 : 0;
         }
-        var text = seriesModel.getFormattedLabel(idx, 'normal')
-                    || data.getName(idx);
-        var textRect = getBoundingRect(
-            text, font, textAlign, 'top'
-        );
+
         hasLabelRotate = !!labelRotate;
         layout.label = {
             x: textX,
@@ -44057,7 +45023,14 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
             textAlign: textAlign,
             verticalAlign: 'middle',
             rotation: labelRotate,
-            inside: isLabelInside
+            inside: isLabelInside,
+            labelDistance: labelDistance,
+            labelAlignTo: labelAlignTo,
+            labelMargin:labelMargin,
+            bleedMargin: bleedMargin,
+            textRect: textRect,
+            text: text,
+            font: font
         };
 
         // Not layout the inside label
@@ -44066,7 +45039,7 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
         }
     });
     if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
-        avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight);
+        avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop);
     }
 };
 
@@ -44093,10 +45066,20 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
 var PI2$4 = Math.PI * 2;
 var RADIAN = Math.PI / 180;
 
+function getViewRect(seriesModel, api) {
+    return getLayoutRect(
+        seriesModel.getBoxLayoutParams(), {
+            width: api.getWidth(),
+            height: api.getHeight()
+        }
+    );
+}
+
 var pieLayout = function (seriesType, ecModel, api, payload) {
     ecModel.eachSeriesByType(seriesType, function (seriesModel) {
         var data = seriesModel.getData();
         var valueDim = data.mapDimension('value');
+        var viewRect = getViewRect(seriesModel, api);
 
         var center = seriesModel.get('center');
         var radius = seriesModel.get('radius');
@@ -44108,11 +45091,11 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
             center = [center, center];
         }
 
-        var width = api.getWidth();
-        var height = api.getHeight();
+        var width = parsePercent$1(viewRect.width, api.getWidth());
+        var height = parsePercent$1(viewRect.height, api.getHeight());
         var size = Math.min(width, height);
-        var cx = parsePercent$1(center[0], width);
-        var cy = parsePercent$1(center[1], height);
+        var cx = parsePercent$1(center[0], width) + viewRect.x;
+        var cy = parsePercent$1(center[1], height) + viewRect.y;
         var r0 = parsePercent$1(radius[0], size / 2);
         var r = parsePercent$1(radius[1], size / 2);
 
@@ -44158,7 +45141,8 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
                     r0: r0,
                     r: roseType
                         ? NaN
-                        : r
+                        : r,
+                    viewRect: viewRect
                 });
                 return;
             }
@@ -44191,7 +45175,8 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
                 r0: r0,
                 r: roseType
                     ? linearMap(value, extent, [r0, r])
-                    : r
+                    : r,
+                viewRect: viewRect
             });
 
             currentAngle = endAngle;
@@ -44229,7 +45214,7 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
             }
         }
 
-        labelLayout(seriesModel, r, width, height);
+        labelLayout(seriesModel, r, viewRect.width, viewRect.height, viewRect.x, viewRect.y);
     });
 };
 
@@ -44340,7 +45325,7 @@ SeriesModel.extend({
     dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'],
 
     getInitialData: function (option, ecModel) {
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     brushSelector: 'point',
@@ -48760,9 +49745,9 @@ var LegendModel = extendComponentModel({
             availableNames.push(seriesName);
             var isPotential;
 
-            if (seriesModel.legendDataProvider) {
-                var data = seriesModel.legendDataProvider();
-                var names = data.mapArray(data.getName);
+            if (seriesModel.legendVisualProvider) {
+                var provider = seriesModel.legendVisualProvider;
+                var names = provider.getAllNames();
 
                 if (!ecModel.isSeriesFiltered(seriesModel)) {
                     availableNames = availableNames.concat(names);
@@ -49351,7 +50336,7 @@ var LegendView = extendComponentView({
                 return;
             }
 
-            // Series legend
+            // Legend to control series.
             if (seriesModel) {
                 var data = seriesModel.getData();
                 var color = data.getVisual('color');
@@ -49380,29 +50365,31 @@ var LegendView = extendComponentView({
                     selectMode
                 );
 
-                itemGroup.on('click', curry$3(dispatchSelectAction, name, api))
+                itemGroup.on('click', curry$3(dispatchSelectAction, name, null, api, excludeSeriesId))
                     .on('mouseover', curry$3(dispatchHighlightAction, seriesModel.name, null, api, excludeSeriesId))
                     .on('mouseout', curry$3(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId));
 
                 legendDrawnMap.set(name, true);
             }
             else {
-                // Data legend of pie, funnel
+                // Legend to control data. In pie and funnel.
                 ecModel.eachRawSeries(function (seriesModel) {
+
                     // In case multiple series has same data name
                     if (legendDrawnMap.get(name)) {
                         return;
                     }
 
-                    if (seriesModel.legendDataProvider) {
-                        var data = seriesModel.legendDataProvider();
-                        var idx = data.indexOfName(name);
-                        if (idx < 0) {
+                    if (seriesModel.legendVisualProvider) {
+                        var provider = seriesModel.legendVisualProvider;
+                        if (!provider.containName(name)) {
                             return;
                         }
 
-                        var color = data.getItemVisual(idx, 'color');
-                        var borderColor = data.getItemVisual(idx, 'borderColor');
+                        var idx = provider.indexOfName(name);
+
+                        var color = provider.getItemVisual(idx, 'color');
+                        var borderColor = provider.getItemVisual(idx, 'borderColor');
 
                         var legendSymbolType = 'roundRect';
 
@@ -49414,7 +50401,7 @@ var LegendView = extendComponentView({
                         );
 
                         // FIXME: consider different series has items with the same name.
-                        itemGroup.on('click', curry$3(dispatchSelectAction, name, api))
+                        itemGroup.on('click', curry$3(dispatchSelectAction, null, name, api, excludeSeriesId))
                             // Should not specify the series name, consider legend controls
                             // more than one pie series.
                             .on('mouseover', curry$3(dispatchHighlightAction, null, name, api, excludeSeriesId))
@@ -49694,11 +50681,15 @@ function setSymbolStyle(symbol, symbolType, legendModelItemStyle, borderColor, i
     return symbol.setStyle(itemStyle);
 }
 
-function dispatchSelectAction(name, api) {
+function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) {
+    // downplay before unselect
+    dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId);
     api.dispatchAction({
         type: 'legendToggleSelect',
-        name: name
+        name: seriesName != null ? seriesName : dataName
     });
+    // highlight after select
+    dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId);
 }
 
 function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) {
@@ -54941,10 +55932,6 @@ var SliderZoomView = DataZoomView.extend({
             draggable: true,
             cursor: getCursor(this._orient),
             drift: bind$3(this._onDragMove, this, 'all'),
-            onmousemove: function (e) {
-                // Fot mobile devicem, prevent screen slider on the button.
-                stop(e.event);
-            },
             ondragstart: bind$3(this._showDataInfo, this, true),
             ondragend: bind$3(this._onDragEnd, this),
             onmouseover: bind$3(this._showDataInfo, this, true),
@@ -54980,10 +55967,6 @@ var SliderZoomView = DataZoomView.extend({
                     cursor: getCursor(this._orient),
                     draggable: true,
                     drift: bind$3(this._onDragMove, this, handleIndex),
-                    onmousemove: function (e) {
-                        // Fot mobile devicem, prevent screen slider on the button.
-                        stop(e.event);
-                    },
                     ondragend: bind$3(this._onDragEnd, this),
                     onmouseover: bind$3(this._showDataInfo, this, true),
                     onmouseout: bind$3(this._showDataInfo, this, false)
@@ -55205,9 +56188,12 @@ var SliderZoomView = DataZoomView.extend({
         handleLabels[1].attr('invisible', !showOrHide);
     },
 
-    _onDragMove: function (handleIndex, dx, dy) {
+    _onDragMove: function (handleIndex, dx, dy, event) {
         this._dragging = true;
 
+        // For mobile device, prevent screen slider on the button.
+        stop(event.event);
+
         // Transform dx, dy to bar coordination.
         var barTransform = this._displayables.barGroup.getLocalTransform();
         var vertex = applyTransform$1([dx, dy], barTransform, true);
@@ -56528,6 +57514,11 @@ extendComponentView({
             var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel);
             var feature;
 
+            // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ?
+            if (payload && payload.newTitle != null) {
+                featureOpt.title = payload.newTitle;
+            }
+
             if (featureName && !oldName) { // Create
                 if (isUserFeatureName(featureName)) {
                     feature = {
@@ -56867,6 +57858,7 @@ register$2(
 */
 
 var magicTypeLang = lang.toolbox.magicType;
+var INNER_STACK_KEYWORD = '__ec_magicType_stack__';
 
 function MagicType(model) {
     this.model = model;
@@ -56880,8 +57872,7 @@ MagicType.defaultOption = {
         /* eslint-disable */
         line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
         bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7',
-        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z', // jshint ignore:line
-        tiled: 'M2.3,2.2h22.8V25H2.3V2.2z M35,2.2h22.8V25H35V2.2zM2.3,35h22.8v22.8H2.3V35z M35,35h22.8v22.8H35V35z'
+        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line
         /* eslint-enable */
     },
     // `line`, `bar`, `stack`, `tiled`
@@ -56932,26 +57923,20 @@ var seriesOptGenreator = {
         }
     },
     'stack': function (seriesType, seriesId, seriesModel, model) {
+        var isStack = seriesModel.get('stack') === INNER_STACK_KEYWORD;
         if (seriesType === 'line' || seriesType === 'bar') {
+            model.setIconStatus('stack', isStack ? 'normal' : 'emphasis');
             return merge({
                 id: seriesId,
-                stack: '__ec_magicType_stack__'
+                stack: isStack ? '' : INNER_STACK_KEYWORD
             }, model.get('option.stack') || {}, true);
         }
-    },
-    'tiled': function (seriesType, seriesId, seriesModel, model) {
-        if (seriesType === 'line' || seriesType === 'bar') {
-            return merge({
-                id: seriesId,
-                stack: ''
-            }, model.get('option.tiled') || {}, true);
-        }
     }
 };
 
 var radioTypes = [
     ['line', 'bar'],
-    ['stack', 'tiled']
+    ['stack']
 ];
 
 proto$3.onclick = function (ecModel, api, type) {
@@ -57016,10 +58001,21 @@ proto$3.onclick = function (ecModel, api, type) {
             }
         }, generateNewSeriesTypes
     );
+
+    var newTitle;
+    // Change title of stack
+    if (type === 'stack') {
+        var isStack = newOption.series && newOption.series[0] && newOption.series[0].stack === INNER_STACK_KEYWORD;
+        newTitle = isStack
+            ? merge({ stack: magicTypeLang.title.tiled }, magicTypeLang.title)
+            : clone(magicTypeLang.title);
+    }
+
     api.dispatchAction({
         type: 'changeMagicType',
         currentType: type,
-        newOption: newOption
+        newOption: newOption,
+        newTitle: newTitle
     });
 };
 
@@ -57634,12 +58630,6 @@ function BrushController(zr) {
 
     /**
      * @private
-     * @type {Object}
-     */
-    this._lastMouseMovePoint = {};
-
-    /**
-     * @private
      * @type {Array}
      */
     this._covers = [];
@@ -57682,7 +58672,8 @@ function BrushController(zr) {
      * @type {Object}
      */
     this._handlers = {};
-    each$16(mouseHandlers, function (handler, eventName) {
+
+    each$16(pointerHandlers, function (handler, eventName) {
         this._handlers[eventName] = bind(handler, this);
     }, this);
 }
@@ -57878,9 +58869,7 @@ function doEnableBrush(controller, brushOption) {
         take(zr, MUTEX_RESOURCE_KEY, controller._uid);
     }
 
-    each$16(controller._handlers, function (handler, eventName) {
-        zr.on(eventName, handler);
-    });
+    mountHandlers(zr, controller._handlers);
 
     controller._brushType = brushOption.brushType;
     controller._brushOption = merge(clone(DEFAULT_BRUSH_OPT), brushOption, true);
@@ -57891,13 +58880,23 @@ function doDisableBrush(controller) {
 
     release(zr, MUTEX_RESOURCE_KEY, controller._uid);
 
-    each$16(controller._handlers, function (handler, eventName) {
-        zr.off(eventName, handler);
-    });
+    unmountHandlers(zr, controller._handlers);
 
     controller._brushType = controller._brushOption = null;
 }
 
+function mountHandlers(zr, handlers) {
+    each$16(handlers, function (handler, eventName) {
+        zr.on(eventName, handler);
+    });
+}
+
+function unmountHandlers(zr, handlers) {
+    each$16(handlers, function (handler, eventName) {
+        zr.off(eventName, handler);
+    });
+}
+
 function createCover(controller, brushOption) {
     var cover = coverRenderers[brushOption.brushType].createCover(controller, brushOption);
     cover.__brushOption = brushOption;
@@ -58211,8 +59210,14 @@ function pointsToRect(points) {
 }
 
 function resetCursor(controller, e, localCursorPoint) {
-    // Check active
-    if (!controller._brushType) {
+    if (
+        // Check active
+        !controller._brushType
+        // resetCursor should be always called when mouse is in zr area,
+        // but not called when mouse is out of zr area to avoid bad influence
+        // if `mousemove`, `mouseup` are triggered from `document` event.
+        || isOutsideZrArea(controller, e)
+    ) {
         return;
     }
 
@@ -58316,7 +59321,7 @@ function determineBrushType(brushType, panel) {
     return brushType;
 }
 
-var mouseHandlers = {
+var pointerHandlers = {
 
     mousedown: function (e) {
         if (this._dragging) {
@@ -58341,56 +59346,34 @@ var mouseHandlers = {
     },
 
     mousemove: function (e) {
-        var lastPoint = this._lastMouseMovePoint;
-        lastPoint.x = e.offsetX;
-        lastPoint.y = e.offsetY;
+        var x = e.offsetX;
+        var y = e.offsetY;
 
-        var localCursorPoint = this.group.transformCoordToLocal(lastPoint.x, lastPoint.y);
+        var localCursorPoint = this.group.transformCoordToLocal(x, y);
 
         resetCursor(this, e, localCursorPoint);
 
         if (this._dragging) {
-
             preventDefault(e);
-
             var eventParams = updateCoverByMouse(this, e, localCursorPoint, false);
-
             eventParams && trigger$1(this, eventParams);
         }
     },
 
     mouseup: function (e) {
         handleDragEnd(this, e);
-    },
-
-    globalout: function (e) {
-        handleDragEnd(this, e, true);
     }
 };
 
-function handleDragEnd(controller, e, isGlobalOut) {
-    if (controller._dragging) {
 
-        // Just be worried about bring some side effect to the world
-        // out of echarts, we do not `preventDefault` for globalout.
-        !isGlobalOut && preventDefault(e);
+function handleDragEnd(controller, e) {
+    if (controller._dragging) {
+        preventDefault(e);
 
-        var pointerX = e.offsetX;
-        var pointerY = e.offsetY;
-        var lastPoint = controller._lastMouseMovePoint;
-        if (isGlobalOut) {
-            pointerX = lastPoint.x;
-            pointerY = lastPoint.y;
-        }
+        var x = e.offsetX;
+        var y = e.offsetY;
 
-        var localCursorPoint = controller.group.transformCoordToLocal(pointerX, pointerY);
-        // FIXME
-        // Here `e` is used only in `onIrrelevantElement` finally. And it's OK
-        // that pass the `e` of `globalout` to `onIrrelevantElement`. But it is
-        // not a good design of these interfaces. However, we do not refactor
-        // these code now because the implementation of `onIrrelevantElement`
-        // need to be discussed and probably be changed in future, becuase it
-        // slows down the performance of zrender in some cases.
+        var localCursorPoint = controller.group.transformCoordToLocal(x, y);
         var eventParams = updateCoverByMouse(controller, e, localCursorPoint, true);
 
         controller._dragging = false;
@@ -58402,6 +59385,12 @@ function handleDragEnd(controller, e, isGlobalOut) {
     }
 }
 
+function isOutsideZrArea(controller, x, y) {
+    var zr = controller._zr;
+    return x < 0 || x > zr.getWidth() || y < 0 || y > zr.getHeight();
+}
+
+
 /**
  * key: brushType
  * @type {Object}
@@ -61178,6 +62167,9 @@ svgPath.brush = function (el) {
     if (style.text != null) {
         svgTextDrawRectText(el, el.getBoundingRect());
     }
+    else {
+        removeOldTextNode(el);
+    }
 };
 
 /***************************************************
@@ -61225,6 +62217,9 @@ svgImage.brush = function (el) {
     if (style.text != null) {
         svgTextDrawRectText(el, el.getBoundingRect());
     }
+    else {
+        removeOldTextNode(el);
+    }
 };
 
 /***************************************************
@@ -61425,6 +62420,18 @@ function updateTextLocation(tspan, textAlign, x, y) {
     attr(tspan, 'y', y);
 }
 
+function removeOldTextNode(el) {
+    if (el && el.__textSvgEl) {
+        // textSvgEl may has no parentNode if el has been removed temporary.
+        if (el.__textSvgEl.parentNode) {
+            el.__textSvgEl.parentNode.removeChild(el.__textSvgEl);
+        }
+        el.__textSvgEl = null;
+        el.__tspanList = [];
+        el.__text = null;
+    }
+}
+
 svgText.drawRectText = svgTextDrawRectText;
 
 svgText.brush = function (el) {
@@ -61432,6 +62439,9 @@ svgText.brush = function (el) {
     if (style.text != null) {
         svgTextDrawRectText(el, false);
     }
+    else {
+        removeOldTextNode(el);
+    }
 };
 
 // Myers' Diff Algorithm
@@ -62686,8 +63696,8 @@ SVGPainter.prototype = {
             else if (!item.removed) {
                 for (var k = 0; k < item.count; k++) {
                     var displayable = newVisibleList[item.indices[k]];
-                    prevSvgElement = getTextSvgElement(displayable)
-                        || getSvgElement(displayable) || prevSvgElement;
+                    var svgElement = getSvgElement(displayable);
+                    var textSvgElement = getTextSvgElement(displayable);
 
                     var svgElement = getSvgElement(displayable);
                     var textSvgElement = getTextSvgElement(displayable);
@@ -62701,6 +63711,12 @@ SVGPainter.prototype = {
                         .addWithoutUpdate(svgElement || textSvgElement, displayable);
 
                     this.clipPathManager.markUsed(displayable);
+
+                    if (textSvgElement) { // Insert text.
+                        insertAfter(svgRoot, textSvgElement, svgElement);
+                    }
+                    prevSvgElement = svgElement
+                        || textSvgElement || prevSvgElement;
                 }
             }
         }
diff --git a/dist/echarts-en.common.min.js b/dist/echarts-en.common.min.js
index cc0634e..4d2d1fb 100644
--- a/dist/echarts-en.common.min.js
+++ b/dist/echarts-en.common.min.js
@@ -19,4 +19,4 @@
 */
 
 
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.echarts={})}(this,function(t){"use strict";var e=2311,n=function(){return e++},m="object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?{browser:{},os:{},node:!1,wxa:!0,canvasSupported:!0,svgSupported:!1,touchEventsSupported:!0,domSupported:!1}:"undefined"==typeof document&&"undefined"!=typeof self?{browser:{},os:{},node:!1,worker:!0,canvasS [...]
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.echarts={})}(this,function(t){"use strict";var e=2311,i=function(){return e++},v="object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?{browser:{},os:{},node:!1,wxa:!0,canvasSupported:!0,svgSupported:!1,touchEventsSupported:!0,domSupported:!1}:"undefined"==typeof document&&"undefined"!=typeof self?{browser:{},os:{},node:!1,worker:!0,canvasS [...]
diff --git a/dist/echarts-en.js b/dist/echarts-en.js
index d713a50..0db470c 100644
--- a/dist/echarts-en.js
+++ b/dist/echarts-en.js
@@ -208,12 +208,17 @@ function detect(ua) {
         // default, so we dont check navigator.maxTouchPoints for them here.
         touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge,
         // <http://caniuse.com/#search=pointer%20event>.
-        pointerEventsSupported: 'onpointerdown' in window
-            // Firefox supports pointer but not by default, only MS browsers are reliable on pointer
+        pointerEventsSupported:
+            // (1) Firefox supports pointer but not by default, only MS browsers are reliable on pointer
             // events currently. So we dont use that on other browsers unless tested sufficiently.
-            // Although IE 10 supports pointer event, it use old style and is different from the
+            // For example, in iOS 13 Mobile Chromium 78, if the touching behavior starts page
+            // scroll, the `pointermove` event can not be fired any more. That will break some
+            // features like "pan horizontally to move something and pan vertically to page scroll".
+            // The horizontal pan probably be interrupted by the casually triggered page scroll.
+            // (2) Although IE 10 supports pointer event, it use old style and is different from the
             // standard. So we exclude that. (IE 10 is hardly used on touch device)
-            && (browser.edge || (browser.ie && browser.version >= 11)),
+            'onpointerdown' in window
+                && (browser.edge || (browser.ie && browser.version >= 11)),
         // passiveSupported: detectPassiveSupport()
         domSupported: typeof document !== 'undefined'
     };
@@ -1262,7 +1267,11 @@ function Draggable() {
     this.on('mousedown', this._dragStart, this);
     this.on('mousemove', this._drag, this);
     this.on('mouseup', this._dragEnd, this);
-    this.on('globalout', this._dragEnd, this);
+    // `mosuemove` and `mouseup` can be continue to fire when dragging.
+    // See [Drag outside] in `Handler.js`. So we do not need to trigger
+    // `_dragEnd` when globalout. That would brings better user experience.
+    // this.on('globalout', this._dragEnd, this);
+
     // this._dropTarget = null;
     // this._draggingTarget = null;
 
@@ -1363,7 +1372,7 @@ var arrySlice = Array.prototype.slice;
  *        param: {string} eventType
  *        param: {string|Object} query
  *        return: {boolean}
- * @param {Function} [eventProcessor.afterTrigger] Call after all handlers called.
+ * @param {Function} [eventProcessor.afterTrigger] Called after all handlers called.
  *        param: {string} eventType
  */
 var Eventful = function (eventProcessor) {
@@ -1413,8 +1422,10 @@ Eventful.prototype = {
     /**
      * Unbind a event.
      *
-     * @param {string} event The event name.
+     * @param {string} [event] The event name.
+     *        If no `event` input, "off" all listeners.
      * @param {Function} [handler] The event handler.
+     *        If no `handler` input, "off" all listeners of the `event`.
      */
     off: function (event, handler) {
         var _h = this._$handlers;
@@ -1572,6 +1583,7 @@ Eventful.prototype = {
     }
 };
 
+
 function normalizeQuery(host, query) {
     var eventProcessor = host._$eventProcessor;
     if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
@@ -1890,6 +1902,18 @@ function preparePointerTransformer(markers, saved) {
 }
 
 /**
+ * Find native event compat for legency IE.
+ * Should be called at the begining of a native event listener.
+ *
+ * @param {Event} [e] Mouse event or touch event or pointer event.
+ *        For lagency IE, we use `window.event` is used.
+ * @return {Event} The native event.
+ */
+function getNativeEvent(e) {
+    return e || window.event;
+}
+
+/**
  * Normalize the coordinates of the input event.
  *
  * Get the `e.zrX` and `e.zrY`, which are relative to the top-left of
@@ -1903,14 +1927,14 @@ function preparePointerTransformer(markers, saved) {
  * between the result coords and the parameters `el` and `calculate`.
  *
  * @param {HTMLElement} el DOM element.
- * @param {Event} [e] Mouse event or touch event. For lagency IE,
- *        do not need to input it and `window.event` is used.
+ * @param {Event} [e] See `getNativeEvent`.
  * @param {boolean} [calculate=false] Whether to force calculate
  *        the coordinates but not use ones provided by browser.
+ * @return {UIEvent} The normalized native UIEvent.
  */
 function normalizeEvent(el, e, calculate) {
 
-    e = e || window.event;
+    e = getNativeEvent(e);
 
     if (e.zrX != null) {
         return e;
@@ -1950,8 +1974,11 @@ function normalizeEvent(el, e, calculate) {
  * @param {HTMLElement} el
  * @param {string} name
  * @param {Function} handler
+ * @param {Object|boolean} opt If boolean, means `opt.capture`
+ * @param {boolean} [opt.capture=false]
+ * @param {boolean} [opt.passive=false]
  */
-function addEventListener(el, name, handler) {
+function addEventListener(el, name, handler, opt) {
     if (isDomLevel2) {
         // Reproduct the console warning:
         // [Violation] Added non-passive event listener to a scroll-blocking <some> event.
@@ -1974,16 +2001,24 @@ function addEventListener(el, name, handler) {
         //     // By default, the third param of el.addEventListener is `capture: false`.
         //     : void 0;
         // el.addEventListener(name, handler /* , opts */);
-        el.addEventListener(name, handler);
+        el.addEventListener(name, handler, opt);
     }
     else {
+        // For simplicity, do not implement `setCapture` for IE9-.
         el.attachEvent('on' + name, handler);
     }
 }
 
-function removeEventListener(el, name, handler) {
+/**
+ * Parameter are the same as `addEventListener`.
+ *
+ * Notice that if a listener is registered twice, one with capture and one without,
+ * remove each one separately. Removal of a capturing listener does not affect a
+ * non-capturing version of the same listener, and vice versa.
+ */
+function removeEventListener(el, name, handler, opt) {
     if (isDomLevel2) {
-        el.removeEventListener(name, handler);
+        el.removeEventListener(name, handler, opt);
     }
     else {
         el.detachEvent('on' + name, handler);
@@ -2138,6 +2173,65 @@ var recognizers = {
     // Only pinch currently.
 };
 
+/**
+ * [The interface between `Handler` and `HandlerProxy`]:
+ *
+ * The default `HandlerProxy` only support the common standard web environment
+ * (e.g., standalone browser, headless browser, embed browser in mobild APP, ...).
+ * But `HandlerProxy` can be replaced to support more non-standard environment
+ * (e.g., mini app), or to support more feature that the default `HandlerProxy`
+ * not provided (like echarts-gl did).
+ * So the interface between `Handler` and `HandlerProxy` should be stable. Do not
+ * make break changes util inevitable. The interface include the public methods
+ * of `Handler` and the events listed in `handlerNames` below, by which `HandlerProxy`
+ * drives `Handler`.
+ */
+
+/**
+ * [Drag outside]:
+ *
+ * That is, triggering `mousemove` and `mouseup` event when the pointer is out of the
+ * zrender area when dragging. That is important for the improvement of the user experience
+ * when dragging something near the boundary without being terminated unexpectedly.
+ *
+ * We originally consider to introduce new events like `pagemovemove` and `pagemouseup`
+ * to resolve this issue. But some drawbacks of it is described in
+ * https://github.com/ecomfe/zrender/pull/536#issuecomment-560286899
+ *
+ * Instead, we referenced the specifications:
+ * https://www.w3.org/TR/touch-events/#the-touchmove-event
+ * https://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#event-type-mousemove
+ * where the the mousemove/touchmove can be continue to fire if the user began a drag
+ * operation and the pointer has left the boundary. (for the mouse event, browsers
+ * only do it on `document` and when the pointer has left the boundary of the browser.)
+ *
+ * So the default `HandlerProxy` supports this feature similarly: if it is in the dragging
+ * state (see `pointerCapture` in `HandlerProxy`), the `mousemove` and `mouseup` continue
+ * to fire until release the pointer. That is implemented by listen to those event on
+ * `document`.
+ * If we implement some other `HandlerProxy` only for touch device, that would be easier.
+ * The touch event support this feature by default.
+ *
+ * Note:
+ * There might be some cases that the mouse event can not be
+ * received on `document`. For example,
+ * (A) `useCapture` is not supported and some user defined event listeners on the ancestor
+ * of zr dom throw Error .
+ * (B) `useCapture` is not supported Some user defined event listeners on the ancestor of
+ * zr dom call `stopPropagation`.
+ * In these cases, the `mousemove` event might be keep triggered event
+ * if the mouse is released. We try to reduce the side-effect in those cases.
+ * That is, do nothing (especially, `findHover`) in those cases. See `isOutsideBoundary`.
+ *
+ * Note:
+ * If `HandlerProxy` listens to `document` with `useCapture`, `HandlerProxy` needs to
+ * make sure `stopPropagation` and `preventDefault` doing nothing if and only if the event
+ * target is not zrender dom. Becuase it is dangerous to enable users to call them in
+ * `document` capture phase to prevent the propagation to any listener of the webpage.
+ * But they are needed to work when the pointer inside the zrender dom.
+ */
+
+
 var SILENT = 'silent';
 
 function makeEventPacket(eveType, targetInfo, event) {
@@ -2162,17 +2256,19 @@ function makeEventPacket(eveType, targetInfo, event) {
     };
 }
 
-function stopEvent(event) {
+function stopEvent() {
     stop(this.event);
 }
 
 function EmptyProxy() {}
 EmptyProxy.prototype.dispose = function () {};
 
+
 var handlerNames = [
     'click', 'dblclick', 'mousewheel', 'mouseout',
     'mouseup', 'mousedown', 'mousemove', 'contextmenu'
 ];
+
 /**
  * @alias module:zrender/Handler
  * @constructor
@@ -2229,7 +2325,6 @@ var Handler = function (storage, painter, proxy, painterRoot) {
      */
     this._gestureMgr;
 
-
     Draggable.call(this);
 
     this.setHandlerProxy(proxy);
@@ -2258,6 +2353,8 @@ Handler.prototype = {
         var x = event.zrX;
         var y = event.zrY;
 
+        var isOutside = isOutsideBoundary(this, x, y);
+
         var lastHovered = this._hovered;
         var lastHoveredTarget = lastHovered.target;
 
@@ -2270,7 +2367,7 @@ Handler.prototype = {
             lastHoveredTarget = lastHovered.target;
         }
 
-        var hovered = this._hovered = this.findHover(x, y);
+        var hovered = this._hovered = isOutside ? {x: x, y: y} : this.findHover(x, y);
         var hoveredTarget = hovered.target;
 
         var proxy = this.proxy;
@@ -2291,23 +2388,18 @@ Handler.prototype = {
     },
 
     mouseout: function (event) {
-        this.dispatchToElement(this._hovered, 'mouseout', event);
+        var eventControl = event.zrEventControl;
+        var zrIsToLocalDOM = event.zrIsToLocalDOM;
 
-        // There might be some doms created by upper layer application
-        // at the same level of painter.getViewportRoot() (e.g., tooltip
-        // dom created by echarts), where 'globalout' event should not
-        // be triggered when mouse enters these doms. (But 'mouseout'
-        // should be triggered at the original hovered element as usual).
-        var element = event.toElement || event.relatedTarget;
-        var innerDom;
-        do {
-            element = element && element.parentNode;
+        if (eventControl !== 'only_globalout') {
+            this.dispatchToElement(this._hovered, 'mouseout', event);
         }
-        while (element && element.nodeType !== 9 && !(
-            innerDom = element === this.painterRoot
-        ));
 
-        !innerDom && this.trigger('globalout', {event: event});
+        if (eventControl !== 'no_globalout') {
+            // FIXME: if the pointer moving from the extra doms to realy "outside",
+            // the `globalout` should have been triggered. But currently not.
+            !zrIsToLocalDOM && this.trigger('globalout', {type: 'globalout', event: event});
+        }
     },
 
     /**
@@ -2453,9 +2545,18 @@ Handler.prototype = {
 // Common handlers
 each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
     Handler.prototype[name] = function (event) {
-        // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
-        var hovered = this.findHover(event.zrX, event.zrY);
-        var hoveredTarget = hovered.target;
+        var x = event.zrX;
+        var y = event.zrY;
+        var isOutside = isOutsideBoundary(this, x, y);
+
+        var hovered;
+        var hoveredTarget;
+
+        if (name !== 'mouseup' || !isOutside) {
+            // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
+            hovered = this.findHover(x, y);
+            hoveredTarget = hovered.target;
+        }
 
         if (name === 'mousedown') {
             this._downEl = hoveredTarget;
@@ -2507,6 +2608,14 @@ function isHover(displayable, x, y) {
     return false;
 }
 
+/**
+ * See [Drag outside].
+ */
+function isOutsideBoundary(handlerInstance, x, y) {
+    var painter = handlerInstance.painter;
+    return x < 0 || x > painter.getWidth() || y < 0 || y > painter.getHeight();
+}
+
 mixin(Handler, Eventful);
 mixin(Handler, Draggable);
 
@@ -10810,30 +10919,55 @@ Animation.prototype = {
 
 mixin(Animation, Eventful);
 
+/* global document */
+
 var TOUCH_CLICK_DELAY = 300;
 
-var mouseHandlerNames = [
-    'click', 'dblclick', 'mousewheel', 'mouseout',
-    'mouseup', 'mousedown', 'mousemove', 'contextmenu'
-];
+var globalEventSupported = env$1.domSupported;
 
-var touchHandlerNames = [
-    'touchstart', 'touchend', 'touchmove'
-];
 
-var pointerEventNames = {
-    pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
+var localNativeListenerNames = (function () {
+    var mouseHandlerNames = [
+        'click', 'dblclick', 'mousewheel', 'mouseout',
+        'mouseup', 'mousedown', 'mousemove', 'contextmenu'
+    ];
+    var touchHandlerNames = [
+        'touchstart', 'touchend', 'touchmove'
+    ];
+    var pointerEventNameMap = {
+        pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
+    };
+    var pointerHandlerNames = map(mouseHandlerNames, function (name) {
+        var nm = name.replace('mouse', 'pointer');
+        return pointerEventNameMap.hasOwnProperty(nm) ? nm : name;
+    });
+
+    return {
+        mouse: mouseHandlerNames,
+        touch: touchHandlerNames,
+        pointer: pointerHandlerNames
+    };
+})();
+
+var globalNativeListenerNames = {
+    mouse: ['mousemove', 'mouseup'],
+    pointer: ['pointermove', 'pointerup']
 };
 
-var pointerHandlerNames = map(mouseHandlerNames, function (name) {
-    var nm = name.replace('mouse', 'pointer');
-    return pointerEventNames[nm] ? nm : name;
-});
 
 function eventNameFix(name) {
     return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' : name;
 }
 
+function isPointerFromTouch(event) {
+    var pointerType = event.pointerType;
+    return pointerType === 'pen' || pointerType === 'touch';
+}
+
+// function useMSGuesture(handlerProxy, event) {
+//     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
+// }
+
 // function onMSGestureChange(proxy, event) {
 //     if (event.translationX || event.translationY) {
 //         // mousemove is carried by MSGesture to reduce the sensitivity.
@@ -10853,117 +10987,174 @@ function eventNameFix(name) {
  * 1. Mobile browsers dispatch mouse events 300ms after touchend.
  * 2. Chrome for Android dispatch mousedown for long-touch about 650ms
  * Result: Blocking Mouse Events for 700ms.
+ *
+ * @param {DOMHandlerScope} scope
  */
-function setTouchTimer(instance) {
-    instance._touching = true;
-    clearTimeout(instance._touchTimer);
-    instance._touchTimer = setTimeout(function () {
-        instance._touching = false;
+function setTouchTimer(scope) {
+    scope.touching = true;
+    if (scope.touchTimer != null) {
+        clearTimeout(scope.touchTimer);
+        scope.touchTimer = null;
+    }
+    scope.touchTimer = setTimeout(function () {
+        scope.touching = false;
+        scope.touchTimer = null;
     }, 700);
 }
 
+// Mark touch, which is useful in distinguish touch and
+// mouse event in upper applicatoin.
+function markTouch(event) {
+    event && (event.zrByTouch = true);
+}
+
+
+// function markTriggeredFromLocal(event) {
+//     event && (event.__zrIsFromLocal = true);
+// }
+
+// function isTriggeredFromLocal(instance, event) {
+//     return !!(event && event.__zrIsFromLocal);
+// }
+
+function normalizeGlobalEvent(instance, event) {
+    // offsetX, offsetY still need to be calculated. They are necessary in the event
+    // handlers of the upper applications. Set `true` to force calculate them.
+    return normalizeEvent(instance.dom, new FakeGlobalEvent(instance, event), true);
+}
+
+/**
+ * Detect whether the given el is in `painterRoot`.
+ */
+function isLocalEl(instance, el) {
+    var isLocal = false;
+    do {
+        el = el && el.parentNode;
+    }
+    while (el && el.nodeType !== 9 && !(
+        isLocal = el === instance.painterRoot
+    ));
+    return isLocal;
+}
+
+/**
+ * Make a fake event but not change the original event,
+ * becuase the global event probably be used by other
+ * listeners not belonging to zrender.
+ * @class
+ */
+function FakeGlobalEvent(instance, event) {
+    this.type = event.type;
+    this.target = this.currentTarget = instance.dom;
+    this.pointerType = event.pointerType;
+    // Necessray for the force calculation of zrX, zrY
+    this.clientX = event.clientX;
+    this.clientY = event.clientY;
+    // Because we do not mount global listeners to touch events,
+    // we do not copy `targetTouches` and `changedTouches` here.
+}
+var fakeGlobalEventProto = FakeGlobalEvent.prototype;
+// we make the default methods on the event do nothing,
+// otherwise it is dangerous. See more details in
+// [Drag outside] in `Handler.js`.
+fakeGlobalEventProto.stopPropagation =
+    fakeGlobalEventProto.stopImmediatePropagation =
+    fakeGlobalEventProto.preventDefault = noop;
+
+
+/**
+ * Local DOM Handlers
+ * @this {HandlerProxy}
+ */
+var localDOMHandlers = {
+
+    mousedown: function (event) {
+        event = normalizeEvent(this.dom, event);
+
+        this._mayPointerCapture = [event.zrX, event.zrY];
+
+        this.trigger('mousedown', event);
+    },
 
-var domHandlers = {
-    /**
-     * Mouse move handler
-     * @inner
-     * @param {Event} event
-     */
     mousemove: function (event) {
         event = normalizeEvent(this.dom, event);
 
+        var downPoint = this._mayPointerCapture;
+        if (downPoint && (event.zrX !== downPoint[0] || event.zrY !== downPoint[1])) {
+            togglePointerCapture(this, true);
+        }
+
         this.trigger('mousemove', event);
     },
 
-    /**
-     * Mouse out handler
-     * @inner
-     * @param {Event} event
-     */
-    mouseout: function (event) {
+    mouseup: function (event) {
         event = normalizeEvent(this.dom, event);
 
-        var element = event.toElement || event.relatedTarget;
-        if (element !== this.dom) {
-            while (element && element.nodeType !== 9) {
-                // 忽略包含在root中的dom引起的mouseOut
-                if (element === this.dom) {
-                    return;
-                }
+        togglePointerCapture(this, false);
 
-                element = element.parentNode;
-            }
+        this.trigger('mouseup', event);
+    },
+
+    mouseout: function (event) {
+        event = normalizeEvent(this.dom, event);
+
+        // Similarly to the browser did on `document` and touch event,
+        // `globalout` will be delayed to final pointer cature release.
+        if (this._pointerCapturing) {
+            event.zrEventControl = 'no_globalout';
         }
 
+        // There might be some doms created by upper layer application
+        // at the same level of painter.getViewportRoot() (e.g., tooltip
+        // dom created by echarts), where 'globalout' event should not
+        // be triggered when mouse enters these doms. (But 'mouseout'
+        // should be triggered at the original hovered element as usual).
+        var element = event.toElement || event.relatedTarget;
+        event.zrIsToLocalDOM = isLocalEl(this, element);
+
         this.trigger('mouseout', event);
     },
 
-    /**
-     * Touch开始响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchstart: function (event) {
         // Default mouse behaviour should not be disabled here.
         // For example, page may needs to be slided.
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this._lastTouchMoment = new Date();
 
         this.handler.processGesture(event, 'start');
 
-        // In touch device, trigger `mousemove`(`mouseover`) should
-        // be triggered, and must before `mousedown` triggered.
-        domHandlers.mousemove.call(this, event);
-
-        domHandlers.mousedown.call(this, event);
-
-        setTouchTimer(this);
+        // For consistent event listener for both touch device and mouse device,
+        // we simulate "mouseover-->mousedown" in touch device. So we trigger
+        // `mousemove` here (to trigger `mouseover` inside), and then trigger
+        // `mousedown`.
+        localDOMHandlers.mousemove.call(this, event);
+        localDOMHandlers.mousedown.call(this, event);
     },
 
-    /**
-     * Touch移动响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchmove: function (event) {
-
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this.handler.processGesture(event, 'change');
 
         // Mouse move should always be triggered no matter whether
         // there is gestrue event, because mouse move and pinch may
         // be used at the same time.
-        domHandlers.mousemove.call(this, event);
-
-        setTouchTimer(this);
+        localDOMHandlers.mousemove.call(this, event);
     },
 
-    /**
-     * Touch结束响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchend: function (event) {
-
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this.handler.processGesture(event, 'end');
 
-        domHandlers.mouseup.call(this, event);
+        localDOMHandlers.mouseup.call(this, event);
 
         // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is
         // triggered in `touchstart`. This seems to be illogical, but by this mechanism,
@@ -10976,14 +11167,12 @@ var domHandlers = {
         // click event should always be triggered no matter whether
         // there is gestrue event. System click can not be prevented.
         if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) {
-            domHandlers.click.call(this, event);
+            localDOMHandlers.click.call(this, event);
         }
-
-        setTouchTimer(this);
     },
 
     pointerdown: function (event) {
-        domHandlers.mousedown.call(this, event);
+        localDOMHandlers.mousedown.call(this, event);
 
         // if (useMSGuesture(this, event)) {
         //     this._msGesture.addPointer(event.pointerId);
@@ -10997,12 +11186,12 @@ var domHandlers = {
         // upper application. So, we dont support mousemove on MS touch
         // device yet.
         if (!isPointerFromTouch(event)) {
-            domHandlers.mousemove.call(this, event);
+            localDOMHandlers.mousemove.call(this, event);
         }
     },
 
     pointerup: function (event) {
-        domHandlers.mouseup.call(this, event);
+        localDOMHandlers.mouseup.call(this, event);
     },
 
     pointerout: function (event) {
@@ -11010,80 +11199,77 @@ var domHandlers = {
         // (IE11+/Edge on MS Surface) after click event triggered,
         // which is inconsistent with the mousout behavior we defined
         // in touchend. So we unify them.
-        // (check domHandlers.touchend for detailed explanation)
+        // (check localDOMHandlers.touchend for detailed explanation)
         if (!isPointerFromTouch(event)) {
-            domHandlers.mouseout.call(this, event);
+            localDOMHandlers.mouseout.call(this, event);
         }
     }
-};
-
-function isPointerFromTouch(event) {
-    var pointerType = event.pointerType;
-    return pointerType === 'pen' || pointerType === 'touch';
-}
 
-// function useMSGuesture(handlerProxy, event) {
-//     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
-// }
+};
 
-// Common handlers
-each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
-    domHandlers[name] = function (event) {
+/**
+ * Othere DOM UI Event handlers for zr dom.
+ * @this {HandlerProxy}
+ */
+each$1(['click', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
+    localDOMHandlers[name] = function (event) {
         event = normalizeEvent(this.dom, event);
         this.trigger(name, event);
     };
 });
 
+
 /**
- * 为控制类实例初始化dom 事件处理函数
+ * DOM UI Event handlers for global page.
  *
- * @inner
- * @param {module:zrender/Handler} instance 控制类实例
+ * [Caution]:
+ * those handlers should both support in capture phase and bubble phase!
+ *
+ * @this {HandlerProxy}
  */
-function initDomHandler(instance) {
-    each$1(touchHandlerNames, function (name) {
-        instance._handlers[name] = bind(domHandlers[name], instance);
-    });
+var globalDOMHandlers = {
 
-    each$1(pointerHandlerNames, function (name) {
-        instance._handlers[name] = bind(domHandlers[name], instance);
-    });
+    pointermove: function (event) {
+        // FIXME
+        // pointermove is so sensitive that it always triggered when
+        // tap(click) on touch screen, which affect some judgement in
+        // upper application. So, we dont support mousemove on MS touch
+        // device yet.
+        if (!isPointerFromTouch(event)) {
+            globalDOMHandlers.mousemove.call(this, event);
+        }
+    },
 
-    each$1(mouseHandlerNames, function (name) {
-        instance._handlers[name] = makeMouseHandler(domHandlers[name], instance);
-    });
+    pointerup: function (event) {
+        globalDOMHandlers.mouseup.call(this, event);
+    },
 
-    function makeMouseHandler(fn, instance) {
-        return function () {
-            if (instance._touching) {
-                return;
-            }
-            return fn.apply(instance, arguments);
-        };
-    }
-}
+    mousemove: function (event) {
+        this.trigger('mousemove', event);
+    },
 
+    mouseup: function (event) {
+        var pointerCaptureReleasing = this._pointerCapturing;
 
-function HandlerDomProxy(dom) {
-    Eventful.call(this);
+        togglePointerCapture(this, false);
 
-    this.dom = dom;
+        this.trigger('mouseup', event);
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._touching = false;
+        if (pointerCaptureReleasing) {
+            event.zrEventControl = 'only_globalout';
+            this.trigger('mouseout', event);
+        }
+    }
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this._touchTimer;
+};
 
-    this._handlers = {};
 
-    initDomHandler(this);
+/**
+ * @param {HandlerProxy} instance
+ * @param {DOMHandlerScope} scope
+ */
+function mountLocalDOMEventListeners(instance, scope) {
+    var domHandlers = scope.domHandlers;
 
     if (env$1.pointerEventsSupported) { // Only IE11+/Edge
         // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240),
@@ -11092,7 +11278,12 @@ function HandlerDomProxy(dom) {
         // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on
         // screen, which do not occurs in pointer event.
         // So we use pointer event to both detect touch gesture and mouse behavior.
-        mountHandlers(pointerHandlerNames, this);
+        each$1(localNativeListenerNames.pointer, function (nativeEventName) {
+            mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                // markTriggeredFromLocal(event);
+                domHandlers[nativeEventName].call(instance, event);
+            });
+        });
 
         // FIXME
         // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable,
@@ -11111,7 +11302,13 @@ function HandlerDomProxy(dom) {
     }
     else {
         if (env$1.touchEventsSupported) {
-            mountHandlers(touchHandlerNames, this);
+            each$1(localNativeListenerNames.touch, function (nativeEventName) {
+                mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                    // markTriggeredFromLocal(event);
+                    domHandlers[nativeEventName].call(instance, event);
+                    setTouchTimer(scope);
+                });
+            });
             // Handler of 'mouseout' event is needed in touch mode, which will be mounted below.
             // addEventListener(root, 'mouseout', this._mouseoutHandler);
         }
@@ -11121,23 +11318,145 @@ function HandlerDomProxy(dom) {
         // mouse event can not be handle in those devices.
         // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent
         // mouseevent after touch event triggered, see `setTouchTimer`.
-        mountHandlers(mouseHandlerNames, this);
+        each$1(localNativeListenerNames.mouse, function (nativeEventName) {
+            mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                event = getNativeEvent(event);
+                if (!scope.touching) {
+                    // markTriggeredFromLocal(event);
+                    domHandlers[nativeEventName].call(instance, event);
+                }
+            });
+        });
+    }
+}
+
+/**
+ * @param {HandlerProxy} instance
+ * @param {DOMHandlerScope} scope
+ */
+function mountGlobalDOMEventListeners(instance, scope) {
+    // Only IE11+/Edge. See the comment in `mountLocalDOMEventListeners`.
+    if (env$1.pointerEventsSupported) {
+        each$1(globalNativeListenerNames.pointer, mount);
+    }
+    // Touch event has implemented "drag outside" so we do not mount global listener for touch event.
+    // (see https://www.w3.org/TR/touch-events/#the-touchmove-event)
+    // We do not consider "both-support-touch-and-mouse device" for this feature (see the comment of
+    // `mountLocalDOMEventListeners`) to avoid bugs util some requirements come.
+    else if (!env$1.touchEventsSupported) {
+        each$1(globalNativeListenerNames.mouse, mount);
+    }
+
+    function mount(nativeEventName) {
+        function nativeEventListener(event) {
+            event = getNativeEvent(event);
+            // See the reason in [Drag outside] in `Handler.js`
+            // This checking supports both `useCapture` or not.
+            // PENDING: if there is performance issue in some devices,
+            // we probably can not use `useCapture` and change a easier
+            // to judes whether local (mark).
+            if (!isLocalEl(instance, event.target)) {
+                event = normalizeGlobalEvent(instance, event);
+                scope.domHandlers[nativeEventName].call(instance, event);
+            }
+        }
+        mountSingleDOMEventListener(
+            scope, nativeEventName, nativeEventListener,
+            {capture: true} // See [Drag Outside] in `Handler.js`
+        );
     }
+}
 
-    function mountHandlers(handlerNames, instance) {
-        each$1(handlerNames, function (name) {
-            addEventListener(dom, eventNameFix(name), instance._handlers[name]);
-        }, instance);
+function mountSingleDOMEventListener(scope, nativeEventName, listener, opt) {
+    scope.mounted[nativeEventName] = listener;
+    scope.listenerOpts[nativeEventName] = opt;
+    addEventListener(scope.domTarget, eventNameFix(nativeEventName), listener, opt);
+}
+
+function unmountDOMEventListeners(scope) {
+    var mounted = scope.mounted;
+    for (var nativeEventName in mounted) {
+        if (mounted.hasOwnProperty(nativeEventName)) {
+            removeEventListener(
+                scope.domTarget, eventNameFix(nativeEventName), mounted[nativeEventName],
+                scope.listenerOpts[nativeEventName]
+            );
+        }
     }
+    scope.mounted = {};
+}
+
+/**
+ * See [Drag Outside] in `Handler.js`.
+ * @implement
+ * @param {boolean} isPointerCapturing Should never be `null`/`undefined`.
+ *        `true`: start to capture pointer if it is not capturing.
+ *        `false`: end the capture if it is capturing.
+ */
+function togglePointerCapture(instance, isPointerCapturing) {
+    instance._mayPointerCapture = null;
+
+    if (globalEventSupported && (instance._pointerCapturing ^ isPointerCapturing)) {
+        instance._pointerCapturing = isPointerCapturing;
+
+        var globalHandlerScope = instance._globalHandlerScope;
+        isPointerCapturing
+            ? mountGlobalDOMEventListeners(instance, globalHandlerScope)
+            : unmountDOMEventListeners(globalHandlerScope);
+    }
+}
+
+/**
+ * @inner
+ * @class
+ */
+function DOMHandlerScope(domTarget, domHandlers) {
+    this.domTarget = domTarget;
+    this.domHandlers = domHandlers;
+
+    // Key: eventName, value: mounted handler funcitons.
+    // Used for unmount.
+    this.mounted = {};
+    this.listenerOpts = {};
+
+    this.touchTimer = null;
+    this.touching = false;
+}
+
+/**
+ * @public
+ * @class
+ */
+function HandlerDomProxy(dom, painterRoot) {
+    Eventful.call(this);
+
+    this.dom = dom;
+    this.painterRoot = painterRoot;
+
+    this._localHandlerScope = new DOMHandlerScope(dom, localDOMHandlers);
+
+    if (globalEventSupported) {
+        this._globalHandlerScope = new DOMHandlerScope(document, globalDOMHandlers);
+    }
+
+    /**
+     * @type {boolean}
+     */
+    this._pointerCapturing = false;
+    /**
+     * @type {Array.<number>} [x, y] or null.
+     */
+    this._mayPointerCapture = null;
+
+    mountLocalDOMEventListeners(this, this._localHandlerScope);
 }
 
 var handlerDomProxyProto = HandlerDomProxy.prototype;
-handlerDomProxyProto.dispose = function () {
-    var handlerNames = mouseHandlerNames.concat(touchHandlerNames);
 
-    for (var i = 0; i < handlerNames.length; i++) {
-        var name = handlerNames[i];
-        removeEventListener(this.dom, eventNameFix(name), this._handlers[name]);
+handlerDomProxyProto.dispose = function () {
+    unmountDOMEventListeners(this._localHandlerScope);
+    if (globalEventSupported) {
+        unmountDOMEventListeners(this._globalHandlerScope);
     }
 };
 
@@ -11145,6 +11464,7 @@ handlerDomProxyProto.setCursor = function (cursorStyle) {
     this.dom.style && (this.dom.style.cursor = cursorStyle || 'default');
 };
 
+
 mixin(HandlerDomProxy, Eventful);
 
 /*!
@@ -11168,7 +11488,7 @@ var instances$1 = {};    // ZRender实例map索引
 /**
  * @type {string}
  */
-var version$1 = '4.1.2';
+var version$1 = '4.2.0';
 
 /**
  * Initializing a zrender instance
@@ -11270,7 +11590,7 @@ var ZRender = function (id, dom, opts) {
     this.storage = storage;
     this.painter = painter;
 
-    var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot()) : null;
+    var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot(), painter.root) : null;
     this.handler = new Handler(storage, painter, handerProxy, painter.root);
 
     /**
@@ -17838,8 +18158,7 @@ function rollbackDefaultTextStyle(style) {
 }
 
 function getFont(opt, ecModel) {
-    // ecModel or default text style model.
-    var gTextStyleModel = ecModel || ecModel.getModel('textStyle');
+    var gTextStyleModel = ecModel && ecModel.getModel('textStyle');
     return trim([
         // FIXME in node-canvas fontWeight is before fontStyle
         opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '',
@@ -20572,178 +20891,6 @@ var colorPaletteMixin = {
 * under the License.
 */
 
-/**
- * Helper for model references.
- * There are many manners to refer axis/coordSys.
- */
-
-// TODO
-// merge relevant logic to this file?
-// check: "modelHelper" of tooltip and "BrushTargetManager".
-
-/**
- * @return {Object} For example:
- * {
- *     coordSysName: 'cartesian2d',
- *     coordSysDims: ['x', 'y', ...],
- *     axisMap: HashMap({
- *         x: xAxisModel,
- *         y: yAxisModel
- *     }),
- *     categoryAxisMap: HashMap({
- *         x: xAxisModel,
- *         y: undefined
- *     }),
- *     // It also indicate that whether there is category axis.
- *     firstCategoryDimIndex: 1,
- *     // To replace user specified encode.
- * }
- */
-function getCoordSysDefineBySeries(seriesModel) {
-    var coordSysName = seriesModel.get('coordinateSystem');
-    var result = {
-        coordSysName: coordSysName,
-        coordSysDims: [],
-        axisMap: createHashMap(),
-        categoryAxisMap: createHashMap()
-    };
-    var fetch = fetchers[coordSysName];
-    if (fetch) {
-        fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
-        return result;
-    }
-}
-
-var fetchers = {
-
-    cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var xAxisModel = seriesModel.getReferringComponents('xAxis')[0];
-        var yAxisModel = seriesModel.getReferringComponents('yAxis')[0];
-
-        if (__DEV__) {
-            if (!xAxisModel) {
-                throw new Error('xAxis "' + retrieve(
-                    seriesModel.get('xAxisIndex'),
-                    seriesModel.get('xAxisId'),
-                    0
-                ) + '" not found');
-            }
-            if (!yAxisModel) {
-                throw new Error('yAxis "' + retrieve(
-                    seriesModel.get('xAxisIndex'),
-                    seriesModel.get('yAxisId'),
-                    0
-                ) + '" not found');
-            }
-        }
-
-        result.coordSysDims = ['x', 'y'];
-        axisMap.set('x', xAxisModel);
-        axisMap.set('y', yAxisModel);
-
-        if (isCategory(xAxisModel)) {
-            categoryAxisMap.set('x', xAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-        if (isCategory(yAxisModel)) {
-            categoryAxisMap.set('y', yAxisModel);
-            result.firstCategoryDimIndex = 1;
-        }
-    },
-
-    singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0];
-
-        if (__DEV__) {
-            if (!singleAxisModel) {
-                throw new Error('singleAxis should be specified.');
-            }
-        }
-
-        result.coordSysDims = ['single'];
-        axisMap.set('single', singleAxisModel);
-
-        if (isCategory(singleAxisModel)) {
-            categoryAxisMap.set('single', singleAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-    },
-
-    polar: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var polarModel = seriesModel.getReferringComponents('polar')[0];
-        var radiusAxisModel = polarModel.findAxisModel('radiusAxis');
-        var angleAxisModel = polarModel.findAxisModel('angleAxis');
-
-        if (__DEV__) {
-            if (!angleAxisModel) {
-                throw new Error('angleAxis option not found');
-            }
-            if (!radiusAxisModel) {
-                throw new Error('radiusAxis option not found');
-            }
-        }
-
-        result.coordSysDims = ['radius', 'angle'];
-        axisMap.set('radius', radiusAxisModel);
-        axisMap.set('angle', angleAxisModel);
-
-        if (isCategory(radiusAxisModel)) {
-            categoryAxisMap.set('radius', radiusAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-        if (isCategory(angleAxisModel)) {
-            categoryAxisMap.set('angle', angleAxisModel);
-            result.firstCategoryDimIndex = 1;
-        }
-    },
-
-    geo: function (seriesModel, result, axisMap, categoryAxisMap) {
-        result.coordSysDims = ['lng', 'lat'];
-    },
-
-    parallel: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var ecModel = seriesModel.ecModel;
-        var parallelModel = ecModel.getComponent(
-            'parallel', seriesModel.get('parallelIndex')
-        );
-        var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice();
-
-        each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) {
-            var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
-            var axisDim = coordSysDims[index];
-            axisMap.set(axisDim, axisModel);
-
-            if (isCategory(axisModel) && result.firstCategoryDimIndex == null) {
-                categoryAxisMap.set(axisDim, axisModel);
-                result.firstCategoryDimIndex = index;
-            }
-        });
-    }
-};
-
-function isCategory(axisModel) {
-    return axisModel.get('type') === 'category';
-}
-
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
 // Avoid typo.
 var SOURCE_FORMAT_ORIGINAL = 'original';
 var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows';
@@ -20913,6 +21060,13 @@ enableClassCheck(Source);
 * under the License.
 */
 
+// The result of `guessOrdinal`.
+var BE_ORDINAL = {
+    Must: 1, // Encounter string but not '-' and not number-like.
+    Might: 2, // Encounter string but number-like.
+    Not: 3 // Other cases
+};
+
 var inner$3 = makeInner();
 
 /**
@@ -21046,14 +21200,6 @@ function prepareSource(seriesModel) {
         data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine
     );
 
-    // Note: dataset option does not have `encode`.
-    var encodeDefine = seriesOption.encode;
-    if (!encodeDefine && datasetModel) {
-        encodeDefine = makeDefaultEncode(
-            seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
-        );
-    }
-
     inner$3(seriesModel).source = new Source({
         data: data,
         fromDataset: fromDataset,
@@ -21062,7 +21208,8 @@ function prepareSource(seriesModel) {
         dimensionsDefine: completeResult.dimensionsDefine,
         startIndex: completeResult.startIndex,
         dimensionsDetectCount: completeResult.dimensionsDetectCount,
-        encodeDefine: encodeDefine
+        // Note: dataset option does not have `encode`.
+        encodeDefine: seriesOption.encode
     });
 }
 
@@ -21074,7 +21221,6 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
 
     var dimensionsDetectCount;
     var startIndex;
-    var findPotentialName;
 
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
         // Rule: Most of the first line are string: it is header.
@@ -21117,13 +21263,11 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
         if (!dimensionsDefine) {
             dimensionsDefine = objectRowsCollectDimensions(data);
-            findPotentialName = true;
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
         if (!dimensionsDefine) {
             dimensionsDefine = [];
-            findPotentialName = true;
             each$1(data, function (colArr, key) {
                 dimensionsDefine.push(key);
             });
@@ -21139,21 +21283,10 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
         }
     }
 
-    var potentialNameDimIndex;
-    if (findPotentialName) {
-        each$1(dimensionsDefine, function (dim, idx) {
-            if ((isObject$1(dim) ? dim.name : dim) === 'name') {
-                potentialNameDimIndex = idx;
-            }
-        });
-    }
-
     return {
         startIndex: startIndex,
         dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
-        dimensionsDetectCount: dimensionsDetectCount,
-        potentialNameDimIndex: potentialNameDimIndex
-        // TODO: potentialIdDimIdx
+        dimensionsDetectCount: dimensionsDetectCount
     };
 }
 
@@ -21227,103 +21360,198 @@ function objectRowsCollectDimensions(data) {
     }
 }
 
-// ??? TODO merge to completedimensions, where also has
-// default encode making logic. And the default rule
-// should depends on series? consider 'map'.
-function makeDefaultEncode(
-    seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
-) {
-    var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
+/**
+ * [The strategy of the arrengment of data dimensions for dataset]:
+ * "value way": all axes are non-category axes. So series one by one take
+ *     several (the number is coordSysDims.length) dimensions from dataset.
+ *     The result of data arrengment of data dimensions like:
+ *     | ser0_x | ser0_y | ser1_x | ser1_y | ser2_x | ser2_y |
+ * "category way": at least one axis is category axis. So the the first data
+ *     dimension is always mapped to the first category axis and shared by
+ *     all of the series. The other data dimensions are taken by series like
+ *     "value way" does.
+ *     The result of data arrengment of data dimensions like:
+ *     | ser_shared_x | ser0_y | ser1_y | ser2_y |
+ *
+ * @param {Array.<Object|string>} coordDimensions [{name: <string>, type: <string>, dimsDef: <Array>}, ...]
+ * @param {module:model/Series} seriesModel
+ * @param {module:data/Source} source
+ * @return {Object} encode Never be `null/undefined`.
+ */
+function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, source) {
     var encode = {};
-    // var encodeTooltip = [];
-    // var encodeLabel = [];
+
+    var datasetModel = getDatasetModel(seriesModel);
+    // Currently only make default when using dataset, util more reqirements occur.
+    if (!datasetModel || !coordDimensions) {
+        return encode;
+    }
+
     var encodeItemName = [];
     var encodeSeriesName = [];
-    var seriesType = seriesModel.subType;
-
-    // ??? TODO refactor: provide by series itself.
-    // Consider the case: 'map' series is based on geo coordSys,
-    // 'graph', 'heatmap' can be based on cartesian. But can not
-    // give default rule simply here.
-    var nSeriesMap = createHashMap(['pie', 'map', 'funnel']);
-    var cSeriesMap = createHashMap([
-        'line', 'bar', 'pictorialBar', 'scatter', 'effectScatter', 'candlestick', 'boxplot'
-    ]);
-
-    // Usually in this case series will use the first data
-    // dimension as the "value" dimension, or other default
-    // processes respectively.
-    if (coordSysDefine && cSeriesMap.get(seriesType) != null) {
-        var ecModel = seriesModel.ecModel;
-        var datasetMap = inner$3(ecModel).datasetMap;
-        var key = datasetModel.uid + '_' + seriesLayoutBy;
-        var datasetRecord = datasetMap.get(key)
-            || datasetMap.set(key, {categoryWayDim: 1, valueWayDim: 0});
 
-        // TODO
-        // Auto detect first time axis and do arrangement.
-        each$1(coordSysDefine.coordSysDims, function (coordDim) {
-            // In value way.
-            if (coordSysDefine.firstCategoryDimIndex == null) {
-                var dataDim = datasetRecord.valueWayDim++;
-                encode[coordDim] = dataDim;
-
-                // ??? TODO give a better default series name rule?
-                // especially when encode x y specified.
-                // consider: when mutiple series share one dimension
-                // category axis, series name should better use
-                // the other dimsion name. On the other hand, use
-                // both dimensions name.
-
-                encodeSeriesName.push(dataDim);
-                // encodeTooltip.push(dataDim);
-                // encodeLabel.push(dataDim);
-            }
-            // In category way, category axis.
-            else if (coordSysDefine.categoryAxisMap.get(coordDim)) {
-                encode[coordDim] = 0;
-                encodeItemName.push(0);
-            }
-            // In category way, non-category axis.
-            else {
-                var dataDim = datasetRecord.categoryWayDim++;
-                encode[coordDim] = dataDim;
-                // encodeTooltip.push(dataDim);
-                // encodeLabel.push(dataDim);
-                encodeSeriesName.push(dataDim);
+    var ecModel = seriesModel.ecModel;
+    var datasetMap = inner$3(ecModel).datasetMap;
+    var key = datasetModel.uid + '_' + source.seriesLayoutBy;
+
+    var baseCategoryDimIndex;
+    var categoryWayValueDimStart;
+    coordDimensions = coordDimensions.slice();
+    each$1(coordDimensions, function (coordDimInfo, coordDimIdx) {
+        !isObject$1(coordDimInfo) && (coordDimensions[coordDimIdx] = {name: coordDimInfo});
+        if (coordDimInfo.type === 'ordinal' && baseCategoryDimIndex == null) {
+            baseCategoryDimIndex = coordDimIdx;
+            categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimensions[coordDimIdx]);
+        }
+        encode[coordDimInfo.name] = [];
+    });
+
+    var datasetRecord = datasetMap.get(key)
+        || datasetMap.set(key, {categoryWayDim: categoryWayValueDimStart, valueWayDim: 0});
+
+    // TODO
+    // Auto detect first time axis and do arrangement.
+    each$1(coordDimensions, function (coordDimInfo, coordDimIdx) {
+        var coordDimName = coordDimInfo.name;
+        var count = getDataDimCountOnCoordDim(coordDimInfo);
+
+        // In value way.
+        if (baseCategoryDimIndex == null) {
+            var start = datasetRecord.valueWayDim;
+            pushDim(encode[coordDimName], start, count);
+            pushDim(encodeSeriesName, start, count);
+            datasetRecord.valueWayDim += count;
+
+            // ??? TODO give a better default series name rule?
+            // especially when encode x y specified.
+            // consider: when mutiple series share one dimension
+            // category axis, series name should better use
+            // the other dimsion name. On the other hand, use
+            // both dimensions name.
+        }
+        // In category way, the first category axis.
+        else if (baseCategoryDimIndex === coordDimIdx) {
+            pushDim(encode[coordDimName], 0, count);
+            pushDim(encodeItemName, 0, count);
+        }
+        // In category way, the other axis.
+        else {
+            var start = datasetRecord.categoryWayDim;
+            pushDim(encode[coordDimName], start, count);
+            pushDim(encodeSeriesName, start, count);
+            datasetRecord.categoryWayDim += count;
+        }
+    });
+
+    function pushDim(dimIdxArr, idxFrom, idxCount) {
+        for (var i = 0; i < idxCount; i++) {
+            dimIdxArr.push(idxFrom + i);
+        }
+    }
+
+    function getDataDimCountOnCoordDim(coordDimInfo) {
+        var dimsDef = coordDimInfo.dimsDef;
+        return dimsDef ? dimsDef.length : 1;
+    }
+
+    encodeItemName.length && (encode.itemName = encodeItemName);
+    encodeSeriesName.length && (encode.seriesName = encodeSeriesName);
+
+    return encode;
+}
+
+/**
+ * Work for data like [{name: ..., value: ...}, ...].
+ *
+ * @param {module:model/Series} seriesModel
+ * @param {module:data/Source} source
+ * @return {Object} encode Never be `null/undefined`.
+ */
+function makeSeriesEncodeForNameBased(seriesModel, source, dimCount) {
+    var encode = {};
+
+    var datasetModel = getDatasetModel(seriesModel);
+    // Currently only make default when using dataset, util more reqirements occur.
+    if (!datasetModel) {
+        return encode;
+    }
+
+    var sourceFormat = source.sourceFormat;
+    var dimensionsDefine = source.dimensionsDefine;
+
+    var potentialNameDimIndex;
+    if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS || sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
+        each$1(dimensionsDefine, function (dim, idx) {
+            if ((isObject$1(dim) ? dim.name : dim) === 'name') {
+                potentialNameDimIndex = idx;
             }
         });
     }
-    // Do not make a complex rule! Hard to code maintain and not necessary.
-    // ??? TODO refactor: provide by series itself.
-    // [{name: ..., value: ...}, ...] like:
-    else if (nSeriesMap.get(seriesType) != null) {
-        // Find the first not ordinal. (5 is an experience value)
-        var firstNotOrdinal;
-        for (var i = 0; i < 5 && firstNotOrdinal == null; i++) {
-            if (!doGuessOrdinal(
-                data, sourceFormat, seriesLayoutBy,
-                completeResult.dimensionsDefine, completeResult.startIndex, i
-            )) {
-                firstNotOrdinal = i;
+
+    // idxResult: {v, n}.
+    var idxResult = (function () {
+
+        var idxRes0 = {};
+        var idxRes1 = {};
+        var guessRecords = [];
+
+        // 5 is an experience value.
+        for (var i = 0, len = Math.min(5, dimCount); i < len; i++) {
+            var guessResult = doGuessOrdinal(
+                source.data, sourceFormat, source.seriesLayoutBy,
+                dimensionsDefine, source.startIndex, i
+            );
+            guessRecords.push(guessResult);
+            var isPureNumber = guessResult === BE_ORDINAL.Not;
+
+            // [Strategy of idxRes0]: find the first BE_ORDINAL.Not as the value dim,
+            // and then find a name dim with the priority:
+            // "BE_ORDINAL.Might|BE_ORDINAL.Must" > "other dim" > "the value dim itself".
+            if (isPureNumber && idxRes0.v == null && i !== potentialNameDimIndex) {
+                idxRes0.v = i;
+            }
+            if (idxRes0.n == null
+                || (idxRes0.n === idxRes0.v)
+                || (!isPureNumber && guessRecords[idxRes0.n] === BE_ORDINAL.Not)
+            ) {
+                idxRes0.n = i;
+            }
+            if (fulfilled(idxRes0) && guessRecords[idxRes0.n] !== BE_ORDINAL.Not) {
+                return idxRes0;
+            }
+
+            // [Strategy of idxRes1]: if idxRes0 not satisfied (that is, no BE_ORDINAL.Not),
+            // find the first BE_ORDINAL.Might as the value dim,
+            // and then find a name dim with the priority:
+            // "other dim" > "the value dim itself".
+            // That is for backward compat: number-like (e.g., `'3'`, `'55'`) can be
+            // treated as number.
+            if (!isPureNumber) {
+                if (guessResult === BE_ORDINAL.Might && idxRes1.v == null && i !== potentialNameDimIndex) {
+                    idxRes1.v = i;
+                }
+                if (idxRes1.n == null || (idxRes1.n === idxRes1.v)) {
+                    idxRes1.n = i;
+                }
             }
         }
-        if (firstNotOrdinal != null) {
-            encode.value = firstNotOrdinal;
-            var nameDimIndex = completeResult.potentialNameDimIndex
-                || Math.max(firstNotOrdinal - 1, 0);
-            // By default, label use itemName in charts.
-            // So we dont set encodeLabel here.
-            encodeSeriesName.push(nameDimIndex);
-            encodeItemName.push(nameDimIndex);
-            // encodeTooltip.push(firstNotOrdinal);
+
+        function fulfilled(idxResult) {
+            return idxResult.v != null && idxResult.n != null;
         }
-    }
 
-    // encodeTooltip.length && (encode.tooltip = encodeTooltip);
-    // encodeLabel.length && (encode.label = encodeLabel);
-    encodeItemName.length && (encode.itemName = encodeItemName);
-    encodeSeriesName.length && (encode.seriesName = encodeSeriesName);
+        return fulfilled(idxRes0) ? idxRes0 : fulfilled(idxRes1) ? idxRes1 : null;
+    })();
+
+    if (idxResult) {
+        encode.value = idxResult.v;
+        // `potentialNameDimIndex` has highest priority.
+        var nameDimIndex = potentialNameDimIndex != null ? potentialNameDimIndex : idxResult.n;
+        // By default, label use itemName in charts.
+        // So we dont set encodeLabel here.
+        encode.itemName = [nameDimIndex];
+        encode.seriesName = [nameDimIndex];
+    }
 
     return encode;
 }
@@ -21351,7 +21579,7 @@ function getDatasetModel(seriesModel) {
  *
  * @param {module:echars/data/Source} source
  * @param {number} dimIndex
- * @return {boolean} Whether ordinal.
+ * @return {BE_ORDINAL} guess result.
  */
 function guessOrdinal(source, dimIndex) {
     return doGuessOrdinal(
@@ -21365,6 +21593,7 @@ function guessOrdinal(source, dimIndex) {
 }
 
 // dimIndex may be overflow source data.
+// return {BE_ORDINAL}
 function doGuessOrdinal(
     data, sourceFormat, seriesLayoutBy, dimensionsDefine, startIndex, dimIndex
 ) {
@@ -21373,15 +21602,26 @@ function doGuessOrdinal(
     var maxLoop = 5;
 
     if (isTypedArray(data)) {
-        return false;
+        return BE_ORDINAL.Not;
     }
 
     // When sourceType is 'objectRows' or 'keyedColumns', dimensionsDefine
     // always exists in source.
     var dimName;
+    var dimType;
     if (dimensionsDefine) {
-        dimName = dimensionsDefine[dimIndex];
-        dimName = isObject$1(dimName) ? dimName.name : dimName;
+        var dimDefItem = dimensionsDefine[dimIndex];
+        if (isObject$1(dimDefItem)) {
+            dimName = dimDefItem.name;
+            dimType = dimDefItem.type;
+        }
+        else if (isString(dimDefItem)) {
+            dimName = dimDefItem;
+        }
+    }
+
+    if (dimType != null) {
+        return dimType === 'ordinal' ? BE_ORDINAL.Must : BE_ORDINAL.Not;
     }
 
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
@@ -21404,7 +21644,7 @@ function doGuessOrdinal(
     }
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
         if (!dimName) {
-            return;
+            return BE_ORDINAL.Not;
         }
         for (var i = 0; i < data.length && i < maxLoop; i++) {
             var item = data[i];
@@ -21415,11 +21655,11 @@ function doGuessOrdinal(
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
         if (!dimName) {
-            return;
+            return BE_ORDINAL.Not;
         }
         var sample = data[dimName];
         if (!sample || isTypedArray(sample)) {
-            return false;
+            return BE_ORDINAL.Not;
         }
         for (var i = 0; i < sample.length && i < maxLoop; i++) {
             if ((result = detectValue(sample[i])) != null) {
@@ -21432,7 +21672,7 @@ function doGuessOrdinal(
             var item = data[i];
             var val = getDataItemValue(item);
             if (!isArray(val)) {
-                return false;
+                return BE_ORDINAL.Not;
             }
             if ((result = detectValue(val[dimIndex])) != null) {
                 return result;
@@ -21441,17 +21681,18 @@ function doGuessOrdinal(
     }
 
     function detectValue(val) {
+        var beStr = isString(val);
         // Consider usage convenience, '1', '2' will be treated as "number".
         // `isFinit('')` get `true`.
         if (val != null && isFinite(val) && val !== '') {
-            return false;
+            return beStr ? BE_ORDINAL.Might : BE_ORDINAL.Not;
         }
-        else if (isString(val) && val !== '-') {
-            return true;
+        else if (beStr && val !== '-') {
+            return BE_ORDINAL.Must;
         }
     }
 
-    return false;
+    return BE_ORDINAL.Not;
 }
 
 /*
@@ -24198,11 +24439,11 @@ var SeriesModel = ComponentModel.extend({
     defaultOption: null,
 
     /**
-     * Data provided for legend
-     * @type {Function}
+     * legend visual provider to the legend component
+     * @type {Object}
      */
     // PENDING
-    legendDataProvider: null,
+    legendVisualProvider: null,
 
     /**
      * Access path of color for visual
@@ -25326,25 +25567,30 @@ var seriesColor = {
     reset: function (seriesModel, ecModel) {
         var data = seriesModel.getData();
         var colorAccessPath = (seriesModel.visualColorAccessPath || 'itemStyle.color').split('.');
-        var color = seriesModel.get(colorAccessPath) // Set in itemStyle
-            || seriesModel.getColorFromPalette(
+        // Set in itemStyle
+        var color = seriesModel.get(colorAccessPath);
+        var colorCallback = (isFunction$1(color) && !(color instanceof Gradient))
+            ? color : null;
+        // Default color
+        if (!color || colorCallback) {
+            color = seriesModel.getColorFromPalette(
                 // TODO series count changed.
                 seriesModel.name, null, ecModel.getSeriesCount()
-            );  // Default color
+            );
+        }
 
-        // FIXME Set color function or use the platte color
         data.setVisual('color', color);
-        
+
         var borderColorAccessPath = (seriesModel.visualBorderColorAccessPath || 'itemStyle.borderColor').split('.');
         var borderColor = seriesModel.get(borderColorAccessPath);
         data.setVisual('borderColor', borderColor);
 
         // Only visible series has each data be visual encoded
         if (!ecModel.isSeriesFiltered(seriesModel)) {
-            if (typeof color === 'function' && !(color instanceof Gradient)) {
+            if (colorCallback) {
                 data.each(function (idx) {
                     data.setItemVisual(
-                        idx, 'color', color(seriesModel.getDataParams(idx))
+                        idx, 'color', colorCallback(seriesModel.getDataParams(idx))
                     );
                 });
             }
@@ -26403,6 +26649,9 @@ var theme = {
             },
             crossStyle: {
                 color: contrastColor
+            },
+            label: {
+                color: '#000'
             }
         }
     },
@@ -27377,10 +27626,10 @@ var isFunction = isFunction$1;
 var isObject = isObject$1;
 var parseClassType = ComponentModel.parseClassType;
 
-var version = '4.5.0';
+var version = '4.6.0';
 
 var dependencies = {
-    zrender: '4.1.2'
+    zrender: '4.2.0'
 };
 
 var TEST_FRAME_REMAIN_TIME = 1;
@@ -30087,6 +30336,138 @@ function mayLabelDimType(dimType) {
 * under the License.
 */
 
+/**
+ * @class
+ * @param {Object|DataDimensionInfo} [opt] All of the fields will be shallow copied.
+ */
+function DataDimensionInfo(opt) {
+    if (opt != null) {
+        extend(this, opt);
+    }
+
+    /**
+     * Dimension name.
+     * Mandatory.
+     * @type {string}
+     */
+    // this.name;
+
+    /**
+     * The origin name in dimsDef, see source helper.
+     * If displayName given, the tooltip will displayed vertically.
+     * Optional.
+     * @type {string}
+     */
+    // this.displayName;
+
+    /**
+     * Which coordSys dimension this dimension mapped to.
+     * A `coordDim` can be a "coordSysDim" that the coordSys required
+     * (for example, an item in `coordSysDims` of `model/referHelper#CoordSysInfo`),
+     * or an generated "extra coord name" if does not mapped to any "coordSysDim"
+     * (That is determined by whether `isExtraCoord` is `true`).
+     * Mandatory.
+     * @type {string}
+     */
+    // this.coordDim;
+
+    /**
+     * The index of this dimension in `series.encode[coordDim]`.
+     * Mandatory.
+     * @type {number}
+     */
+    // this.coordDimIndex;
+
+    /**
+     * Dimension type. The enumerable values are the key of
+     * `dataCtors` of `data/List`.
+     * Optional.
+     * @type {string}
+     */
+    // this.type;
+
+    /**
+     * This index of this dimension info in `data/List#_dimensionInfos`.
+     * Mandatory after added to `data/List`.
+     * @type {number}
+     */
+    // this.index;
+
+    /**
+     * The format of `otherDims` is:
+     * ```js
+     * {
+     *     tooltip: number optional,
+     *     label: number optional,
+     *     itemName: number optional,
+     *     seriesName: number optional,
+     * }
+     * ```
+     *
+     * A `series.encode` can specified these fields:
+     * ```js
+     * encode: {
+     *     // "3, 1, 5" is the index of data dimension.
+     *     tooltip: [3, 1, 5],
+     *     label: [0, 3],
+     *     ...
+     * }
+     * ```
+     * `otherDims` is the parse result of the `series.encode` above, like:
+     * ```js
+     * // Suppose the index of this data dimension is `3`.
+     * this.otherDims = {
+     *     // `3` is at the index `0` of the `encode.tooltip`
+     *     tooltip: 0,
+     *     // `3` is at the index `1` of the `encode.tooltip`
+     *     label: 1
+     * };
+     * ```
+     *
+     * This prop should never be `null`/`undefined` after initialized.
+     * @type {Object}
+     */
+    this.otherDims = {};
+
+    /**
+     * Be `true` if this dimension is not mapped to any "coordSysDim" that the
+     * "coordSys" required.
+     * Mandatory.
+     * @type {boolean}
+     */
+    // this.isExtraCoord;
+
+    /**
+     * @type {module:data/OrdinalMeta}
+     */
+    // this.ordinalMeta;
+
+    /**
+     * Whether to create inverted indices.
+     * @type {boolean}
+     */
+    // this.createInvertedIndices;
+}
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
 /* global Float64Array, Int32Array, Uint32Array, Uint16Array */
 
 /**
@@ -30164,13 +30545,9 @@ function transferProperties(target, source) {
  * @constructor
  * @alias module:echarts/data/List
  *
- * @param {Array.<string|Object>} dimensions
+ * @param {Array.<string|Object|module:data/DataDimensionInfo>} dimensions
  *      For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
  *      Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
- *      Spetial fields: {
- *          ordinalMeta: <module:echarts/data/OrdinalMeta>
- *          createInvertedIndices: <boolean>
- *      }
  * @param {module:echarts/model/Model} hostModel
  */
 var List = function (dimensions, hostModel) {
@@ -30186,7 +30563,10 @@ var List = function (dimensions, hostModel) {
         var dimensionInfo = dimensions[i];
 
         if (isString(dimensionInfo)) {
-            dimensionInfo = {name: dimensionInfo};
+            dimensionInfo = new DataDimensionInfo({name: dimensionInfo});
+        }
+        else if (!(dimensionInfo instanceof DataDimensionInfo)) {
+            dimensionInfo = new DataDimensionInfo(dimensionInfo);
         }
 
         var dimensionName = dimensionInfo.name;
@@ -31206,7 +31586,8 @@ listProto.indexOfRawIndex = function (rawIndex) {
  * @param {string} dim
  * @param {number} value
  * @param {number} [maxDistance=Infinity]
- * @return {Array.<number>} Considere multiple points has the same value.
+ * @return {Array.<number>} If and only if multiple indices has
+ *        the same value, they are put to the result.
  */
 listProto.indicesOfNearest = function (dim, value, maxDistance) {
     var storage = this._storage;
@@ -31221,23 +31602,35 @@ listProto.indicesOfNearest = function (dim, value, maxDistance) {
         maxDistance = Infinity;
     }
 
-    var minDist = Number.MAX_VALUE;
+    var minDist = Infinity;
     var minDiff = -1;
+    var nearestIndicesLen = 0;
+
+    // Check the test case of `test/ut/spec/data/List.js`.
     for (var i = 0, len = this.count(); i < len; i++) {
-        var diff = value - this.get(dim, i /*, stack */);
+        var diff = value - this.get(dim, i);
         var dist = Math.abs(diff);
-        if (diff <= maxDistance && dist <= minDist) {
-            // For the case of two data are same on xAxis, which has sequence data.
-            // Show the nearest index
-            // https://github.com/ecomfe/echarts/issues/2869
-            if (dist < minDist || (diff >= 0 && minDiff < 0)) {
+        if (dist <= maxDistance) {
+            // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`,
+            // we'd better not push both of them to `nearestIndices`, otherwise it is easy to
+            // get more than one item in `nearestIndices` (more specifically, in `tooltip`).
+            // So we chose the one that `diff >= 0` in this csae.
+            // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them
+            // should be push to `nearestIndices`.
+            if (dist < minDist
+                || (dist === minDist && diff >= 0 && minDiff < 0)
+            ) {
                 minDist = dist;
                 minDiff = diff;
-                nearestIndices.length = 0;
+                nearestIndicesLen = 0;
+            }
+            if (diff === minDiff) {
+                nearestIndices[nearestIndicesLen++] = i;
             }
-            nearestIndices.push(i);
         }
     }
+    nearestIndices.length = nearestIndicesLen;
+
     return nearestIndices;
 };
 
@@ -32111,8 +32504,12 @@ listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
 /**
  * @see {module:echarts/test/ut/spec/data/completeDimensions}
  *
- * Complete the dimensions array, by user defined `dimension` and `encode`,
- * and guessing from the data structure.
+ * This method builds the relationship between:
+ * + "what the coord sys or series requires (see `sysDims`)",
+ * + "what the user defines (in `encode` and `dimensions`, see `opt.dimsDef` and `opt.encodeDef`)"
+ * + "what the data source provids (see `source`)".
+ *
+ * Some guess strategy will be adapted if user does not define something.
  * If no 'value' dimension specified, the first no-named dimension will be
  * named as 'value'.
  *
@@ -32128,32 +32525,20 @@ listProto.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
  * @param {Array.<Object|string>} [opt.dimsDef] option.series.dimensions User defined dimensions
  *      For example: ['asdf', {name, type}, ...].
  * @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1], tooltip: [1, 2], label: 3}
+ * @param {Function} [opt.encodeDefaulter] Called if no `opt.encodeDef` exists.
+ *      If not specified, auto find the next available data dim.
+ *      param source {module:data/Source}
+ *      param dimCount {number}
+ *      return {Object} encode Never be `null/undefined`.
  * @param {string} [opt.generateCoord] Generate coord dim with the given name.
- *                 If not specified, extra dim names will be:
- *                 'value', 'value0', 'value1', ...
+ *      If not specified, extra dim names will be:
+ *      'value', 'value0', 'value1', ...
  * @param {number} [opt.generateCoordCount] By default, the generated dim name is `generateCoord`.
- *                 If `generateCoordCount` specified, the generated dim names will be:
- *                 `generateCoord` + 0, `generateCoord` + 1, ...
- *                 can be Infinity, indicate that use all of the remain columns.
+ *      If `generateCoordCount` specified, the generated dim names will be:
+ *      `generateCoord` + 0, `generateCoord` + 1, ...
+ *      can be Infinity, indicate that use all of the remain columns.
  * @param {number} [opt.dimCount] If not specified, guess by the first data item.
- * @param {number} [opt.encodeDefaulter] If not specified, auto find the next available data dim.
- * @return {Array.<Object>} [{
- *      name: string mandatory,
- *      displayName: string, the origin name in dimsDef, see source helper.
- *                 If displayName given, the tooltip will displayed vertically.
- *      coordDim: string mandatory,
- *      coordDimIndex: number mandatory,
- *      type: string optional,
- *      otherDims: { never null/undefined
- *          tooltip: number optional,
- *          label: number optional,
- *          itemName: number optional,
- *          seriesName: number optional,
- *      },
- *      isExtraCoord: boolean true if coord is generated
- *          (not specified in encode and not series specified)
- *      other props ...
- * }]
+ * @return {Array.<module:data/DataDimensionInfo>}
  */
 function completeDimensions(sysDims, source, opt) {
     if (!Source.isInstance(source)) {
@@ -32163,7 +32548,6 @@ function completeDimensions(sysDims, source, opt) {
     opt = opt || {};
     sysDims = (sysDims || []).slice();
     var dimsDef = (opt.dimsDef || []).slice();
-    var encodeDef = createHashMap(opt.encodeDef);
     var dataDimNameMap = createHashMap();
     var coordDimNameMap = createHashMap();
     // var valueCandidate;
@@ -32177,7 +32561,7 @@ function completeDimensions(sysDims, source, opt) {
             {}, isObject$1(dimsDef[i]) ? dimsDef[i] : {name: dimsDef[i]}
         );
         var userDimName = dimDefItem.name;
-        var resultItem = result[i] = {otherDims: {}};
+        var resultItem = result[i] = new DataDimensionInfo();
         // Name will be applied later for avoiding duplication.
         if (userDimName != null && dataDimNameMap.get(userDimName) == null) {
             // Only if `series.dimensions` is defined in option
@@ -32190,6 +32574,12 @@ function completeDimensions(sysDims, source, opt) {
         dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName);
     }
 
+    var encodeDef = opt.encodeDef;
+    if (!encodeDef && opt.encodeDefaulter) {
+        encodeDef = opt.encodeDefaulter(source, dimCount);
+    }
+    encodeDef = createHashMap(encodeDef);
+
     // Set `coordDim` and `coordDimIndex` by `encodeDef` and normalize `encodeDef`.
     encodeDef.each(function (dataDims, coordDim) {
         dataDims = normalizeToArray(dataDims).slice();
@@ -32291,7 +32681,7 @@ function completeDimensions(sysDims, source, opt) {
 
     // Set dim `name` and other `coordDim` and other props.
     for (var resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) {
-        var resultItem = result[resultDimIdx] = result[resultDimIdx] || {};
+        var resultItem = result[resultDimIdx] = result[resultDimIdx] || new DataDimensionInfo();
         var coordDim = resultItem.coordDim;
 
         if (coordDim == null) {
@@ -32310,7 +32700,28 @@ function completeDimensions(sysDims, source, opt) {
             dataDimNameMap
         ));
 
-        if (resultItem.type == null && guessOrdinal(source, resultDimIdx, resultItem.name)) {
+        if (resultItem.type == null
+            && (
+                guessOrdinal(source, resultDimIdx, resultItem.name) === BE_ORDINAL.Must
+                // Consider the case:
+                // {
+                //    dataset: {source: [
+                //        ['2001', 123],
+                //        ['2002', 456],
+                //        ...
+                //        ['The others', 987],
+                //    ]},
+                //    series: {type: 'pie'}
+                // }
+                // The first colum should better be treated as a "ordinal" although it
+                // might not able to be detected as an "ordinal" by `guessOrdinal`.
+                || (resultItem.isExtraCoord
+                    && (resultItem.otherDims.itemName != null
+                        || resultItem.otherDims.seriesName != null
+                    )
+                )
+            )
+        ) {
             resultItem.type = 'ordinal';
         }
     }
@@ -32388,6 +32799,7 @@ function genName(name, map$$1, fromZero) {
  * @param {string} [opt.generateCoordCount]
  * @param {Array.<string|Object>} [opt.dimensionsDefine=source.dimensionsDefine] Overwrite source define.
  * @param {Object|HashMap} [opt.encodeDefine=source.encodeDefine] Overwrite source define.
+ * @param {Function} [opt.encodeDefaulter] Make default encode if user not specified.
  * @return {Array.<Object>} dimensionsInfo
  */
 var createDimensions = function (source, opt) {
@@ -32396,6 +32808,7 @@ var createDimensions = function (source, opt) {
         dimsDef: opt.dimensionsDefine || source.dimensionsDefine,
         encodeDef: opt.encodeDefine || source.encodeDefine,
         dimCount: opt.dimensionsCount,
+        encodeDefaulter: opt.encodeDefaulter,
         generateCoord: opt.generateCoord,
         generateCoordCount: opt.generateCoordCount
     });
@@ -32421,6 +32834,201 @@ var createDimensions = function (source, opt) {
 */
 
 /**
+ * Helper for model references.
+ * There are many manners to refer axis/coordSys.
+ */
+
+// TODO
+// merge relevant logic to this file?
+// check: "modelHelper" of tooltip and "BrushTargetManager".
+
+/**
+ * @class
+ * For example:
+ * {
+ *     coordSysName: 'cartesian2d',
+ *     coordSysDims: ['x', 'y', ...],
+ *     axisMap: HashMap({
+ *         x: xAxisModel,
+ *         y: yAxisModel
+ *     }),
+ *     categoryAxisMap: HashMap({
+ *         x: xAxisModel,
+ *         y: undefined
+ *     }),
+ *     // The index of the first category axis in `coordSysDims`.
+ *     // `null/undefined` means no category axis exists.
+ *     firstCategoryDimIndex: 1,
+ *     // To replace user specified encode.
+ * }
+ */
+function CoordSysInfo(coordSysName) {
+    /**
+     * @type {string}
+     */
+    this.coordSysName = coordSysName;
+    /**
+     * @type {Array.<string>}
+     */
+    this.coordSysDims = [];
+    /**
+     * @type {module:zrender/core/util#HashMap}
+     */
+    this.axisMap = createHashMap();
+    /**
+     * @type {module:zrender/core/util#HashMap}
+     */
+    this.categoryAxisMap = createHashMap();
+    /**
+     * @type {number}
+     */
+    this.firstCategoryDimIndex = null;
+}
+
+/**
+ * @return {module:model/referHelper#CoordSysInfo}
+ */
+function getCoordSysInfoBySeries(seriesModel) {
+    var coordSysName = seriesModel.get('coordinateSystem');
+    var result = new CoordSysInfo(coordSysName);
+    var fetch = fetchers[coordSysName];
+    if (fetch) {
+        fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
+        return result;
+    }
+}
+
+var fetchers = {
+
+    cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var xAxisModel = seriesModel.getReferringComponents('xAxis')[0];
+        var yAxisModel = seriesModel.getReferringComponents('yAxis')[0];
+
+        if (__DEV__) {
+            if (!xAxisModel) {
+                throw new Error('xAxis "' + retrieve(
+                    seriesModel.get('xAxisIndex'),
+                    seriesModel.get('xAxisId'),
+                    0
+                ) + '" not found');
+            }
+            if (!yAxisModel) {
+                throw new Error('yAxis "' + retrieve(
+                    seriesModel.get('xAxisIndex'),
+                    seriesModel.get('yAxisId'),
+                    0
+                ) + '" not found');
+            }
+        }
+
+        result.coordSysDims = ['x', 'y'];
+        axisMap.set('x', xAxisModel);
+        axisMap.set('y', yAxisModel);
+
+        if (isCategory(xAxisModel)) {
+            categoryAxisMap.set('x', xAxisModel);
+            result.firstCategoryDimIndex = 0;
+        }
+        if (isCategory(yAxisModel)) {
+            categoryAxisMap.set('y', yAxisModel);
+            result.firstCategoryDimIndex == null & (result.firstCategoryDimIndex = 1);
+        }
+    },
+
+    singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0];
+
+        if (__DEV__) {
+            if (!singleAxisModel) {
+                throw new Error('singleAxis should be specified.');
+            }
+        }
+
+        result.coordSysDims = ['single'];
+        axisMap.set('single', singleAxisModel);
+
+        if (isCategory(singleAxisModel)) {
+            categoryAxisMap.set('single', singleAxisModel);
+            result.firstCategoryDimIndex = 0;
+        }
+    },
+
+    polar: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var polarModel = seriesModel.getReferringComponents('polar')[0];
+        var radiusAxisModel = polarModel.findAxisModel('radiusAxis');
+        var angleAxisModel = polarModel.findAxisModel('angleAxis');
+
+        if (__DEV__) {
+            if (!angleAxisModel) {
+                throw new Error('angleAxis option not found');
+            }
+            if (!radiusAxisModel) {
+                throw new Error('radiusAxis option not found');
+            }
+        }
+
+        result.coordSysDims = ['radius', 'angle'];
+        axisMap.set('radius', radiusAxisModel);
+        axisMap.set('angle', angleAxisModel);
+
+        if (isCategory(radiusAxisModel)) {
+            categoryAxisMap.set('radius', radiusAxisModel);
+            result.firstCategoryDimIndex = 0;
+        }
+        if (isCategory(angleAxisModel)) {
+            categoryAxisMap.set('angle', angleAxisModel);
+            result.firstCategoryDimIndex == null && (result.firstCategoryDimIndex = 1);
+        }
+    },
+
+    geo: function (seriesModel, result, axisMap, categoryAxisMap) {
+        result.coordSysDims = ['lng', 'lat'];
+    },
+
+    parallel: function (seriesModel, result, axisMap, categoryAxisMap) {
+        var ecModel = seriesModel.ecModel;
+        var parallelModel = ecModel.getComponent(
+            'parallel', seriesModel.get('parallelIndex')
+        );
+        var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice();
+
+        each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) {
+            var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
+            var axisDim = coordSysDims[index];
+            axisMap.set(axisDim, axisModel);
+
+            if (isCategory(axisModel) && result.firstCategoryDimIndex == null) {
+                categoryAxisMap.set(axisDim, axisModel);
+                result.firstCategoryDimIndex = index;
+            }
+        });
+    }
+};
+
+function isCategory(axisModel) {
+    return axisModel.get('type') === 'category';
+}
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+/**
  * Note that it is too complicated to support 3d stack by value
  * (have to create two-dimension inverted index), so in 3d case
  * we just support that stacked by index.
@@ -32586,6 +33194,7 @@ function getStackedDimension(data, targetDim) {
  * @param {module:echarts/model/Series} seriesModel
  * @param {Object} [opt]
  * @param {string} [opt.generateCoord]
+ * @param {boolean} [opt.useEncodeDefaulter]
  */
 function createListFromArray(source, seriesModel, opt) {
     opt = opt || {};
@@ -32597,14 +33206,14 @@ function createListFromArray(source, seriesModel, opt) {
     var coordSysName = seriesModel.get('coordinateSystem');
     var registeredCoordSys = CoordinateSystemManager.get(coordSysName);
 
-    var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
+    var coordSysInfo = getCoordSysInfoBySeries(seriesModel);
 
     var coordSysDimDefs;
 
-    if (coordSysDefine) {
-        coordSysDimDefs = map(coordSysDefine.coordSysDims, function (dim) {
+    if (coordSysInfo) {
+        coordSysDimDefs = map(coordSysInfo.coordSysDims, function (dim) {
             var dimInfo = {name: dim};
-            var axisModel = coordSysDefine.axisMap.get(dim);
+            var axisModel = coordSysInfo.axisMap.get(dim);
             if (axisModel) {
                 var axisType = axisModel.get('type');
                 dimInfo.type = getDimensionTypeByAxis(axisType);
@@ -32625,14 +33234,17 @@ function createListFromArray(source, seriesModel, opt) {
 
     var dimInfoList = createDimensions(source, {
         coordDimensions: coordSysDimDefs,
-        generateCoord: opt.generateCoord
+        generateCoord: opt.generateCoord,
+        encodeDefaulter: opt.useEncodeDefaulter
+            ? curry(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel)
+            : null
     });
 
     var firstCategoryDimIndex;
     var hasNameEncode;
-    coordSysDefine && each$1(dimInfoList, function (dimInfo, dimIndex) {
+    coordSysInfo && each$1(dimInfoList, function (dimInfo, dimIndex) {
         var coordDim = dimInfo.coordDim;
-        var categoryAxisModel = coordSysDefine.categoryAxisMap.get(coordDim);
+        var categoryAxisModel = coordSysInfo.categoryAxisMap.get(coordDim);
         if (categoryAxisModel) {
             if (firstCategoryDimIndex == null) {
                 firstCategoryDimIndex = dimIndex;
@@ -33204,44 +33816,6 @@ function fixExtent(niceTickExtent, extent) {
     }
 }
 
-function intervalScaleGetTicks(interval, extent, niceTickExtent, intervalPrecision) {
-    var ticks = [];
-
-    // If interval is 0, return [];
-    if (!interval) {
-        return ticks;
-    }
-
-    // Consider this case: using dataZoom toolbox, zoom and zoom.
-    var safeLimit = 10000;
-
-    if (extent[0] < niceTickExtent[0]) {
-        ticks.push(extent[0]);
-    }
-    var tick = niceTickExtent[0];
-
-    while (tick <= niceTickExtent[1]) {
-        ticks.push(tick);
-        // Avoid rounding error
-        tick = roundNumber$1(tick + interval, intervalPrecision);
-        if (tick === ticks[ticks.length - 1]) {
-            // Consider out of safe float point, e.g.,
-            // -3711126.9907707 + 2e-10 === -3711126.9907707
-            break;
-        }
-        if (ticks.length > safeLimit) {
-            return [];
-        }
-    }
-    // Consider this case: the last item of ticks is smaller
-    // than niceTickExtent[1] and niceTickExtent[1] === extent[1].
-    if (extent[1] > (ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1])) {
-        ticks.push(extent[1]);
-    }
-
-    return ticks;
-}
-
 /*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
@@ -33320,12 +33894,92 @@ var IntervalScale = Scale.extend({
     },
 
     /**
+     * @param {boolean} [expandToNicedExtent=false] If expand the ticks to niced extent.
      * @return {Array.<number>}
      */
-    getTicks: function () {
-        return intervalScaleGetTicks(
-            this._interval, this._extent, this._niceExtent, this._intervalPrecision
-        );
+    getTicks: function (expandToNicedExtent) {
+        var interval = this._interval;
+        var extent = this._extent;
+        var niceTickExtent = this._niceExtent;
+        var intervalPrecision = this._intervalPrecision;
+
+        var ticks = [];
+        // If interval is 0, return [];
+        if (!interval) {
+            return ticks;
+        }
+
+        // Consider this case: using dataZoom toolbox, zoom and zoom.
+        var safeLimit = 10000;
+
+        if (extent[0] < niceTickExtent[0]) {
+            if (expandToNicedExtent) {
+                ticks.push(roundNumber(niceTickExtent[0] - interval));
+            }
+            else {
+                ticks.push(extent[0]);
+            }
+        }
+        var tick = niceTickExtent[0];
+
+        while (tick <= niceTickExtent[1]) {
+            ticks.push(tick);
+            // Avoid rounding error
+            tick = roundNumber(tick + interval, intervalPrecision);
+            if (tick === ticks[ticks.length - 1]) {
+                // Consider out of safe float point, e.g.,
+                // -3711126.9907707 + 2e-10 === -3711126.9907707
+                break;
+            }
+            if (ticks.length > safeLimit) {
+                return [];
+            }
+        }
+        // Consider this case: the last item of ticks is smaller
+        // than niceTickExtent[1] and niceTickExtent[1] === extent[1].
+        var lastNiceTick = ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1];
+        if (extent[1] > lastNiceTick) {
+            if (expandToNicedExtent) {
+                ticks.push(lastNiceTick + interval);
+            }
+            else {
+                ticks.push(extent[1]);
+            }
+        }
+
+        return ticks;
+    },
+
+    /**
+     * @param {number} [splitNumber=5]
+     * @return {Array.<Array.<number>>}
+     */
+    getMinorTicks: function (splitNumber) {
+        var ticks = this.getTicks(true);
+        var minorTicks = [];
+        var extent = this.getExtent();
+
+        for (var i = 1; i < ticks.length; i++) {
+            var nextTick = ticks[i];
+            var prevTick = ticks[i - 1];
+            var count = 0;
+            var minorTicksGroup = [];
+            var interval = nextTick - prevTick;
+            var minorInterval = interval / splitNumber;
+
+            while (count < splitNumber - 1) {
+                var minorTick = round$1(prevTick + (count + 1) * minorInterval);
+
+                // For the first and last interval. The count may be less than splitNumber.
+                if (minorTick > extent[0] && minorTick < extent[1]) {
+                    minorTicksGroup.push(minorTick);
+                }
+                count++;
+            }
+            minorTicks.push(minorTicksGroup);
+        }
+
+        return minorTicks;
     },
 
     /**
@@ -33691,7 +34345,6 @@ function doCalBarWidthAndOffset(seriesInfoList) {
         // only the attributes set on the last series will work.
         // Do not change this fact unless there will be a break change.
 
-        // TODO
         var barWidth = seriesInfo.barWidth;
         if (barWidth && !stacks[stackId].width) {
             // See #6312, do not restrict width.
@@ -33731,6 +34384,7 @@ function doCalBarWidthAndOffset(seriesInfoList) {
         each$1(stacks, function (column) {
             var maxWidth = column.maxWidth;
             var minWidth = column.minWidth;
+
             if (!column.width) {
                 var finalWidth = autoWidth;
                 if (maxWidth && maxWidth < finalWidth) {
@@ -33746,7 +34400,7 @@ function doCalBarWidthAndOffset(seriesInfoList) {
                 }
                 if (finalWidth !== autoWidth) {
                     column.width = finalWidth;
-                    remainedWidth -= finalWidth;
+                    remainedWidth -= finalWidth + barGapPercent * finalWidth;
                     autoWidthCount--;
                 }
             }
@@ -33763,7 +34417,7 @@ function doCalBarWidthAndOffset(seriesInfoList) {
                     finalWidth = Math.max(finalWidth, minWidth);
                 }
                 column.width = finalWidth;
-                remainedWidth -= finalWidth;
+                remainedWidth -= finalWidth + barGapPercent * finalWidth;
                 autoWidthCount--;
             }
         });
@@ -33771,8 +34425,10 @@ function doCalBarWidthAndOffset(seriesInfoList) {
         // Recalculate width again
         autoWidth = (remainedWidth - categoryGap)
             / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
+
         autoWidth = Math.max(autoWidth, 0);
 
+
         var widthSum = 0;
         var lastColumn;
         each$1(stacks, function (column, idx) {
@@ -33911,6 +34567,7 @@ function layout(seriesType, ecModel) {
                 }
                 stacked && (lastStackCoords[stackId][baseValue][sign] += height);
             }
+
             data.setItemLayout(idx, {
                 x: x,
                 y: y,
@@ -34271,14 +34928,15 @@ var LogScale = Scale.extend({
     },
 
     /**
+     * @param {boolean} [expandToNicedExtent=false] If expand the ticks to niced extent.
      * @return {Array.<number>}
      */
-    getTicks: function () {
+    getTicks: function (expandToNicedExtent) {
         var originalScale = this._originalScale;
         var extent = this._extent;
         var originalExtent = originalScale.getExtent();
 
-        return map(intervalScaleProto$1.getTicks.call(this), function (val) {
+        return map(intervalScaleProto$1.getTicks.call(this, expandToNicedExtent), function (val) {
             var powVal = round$1(mathPow$1(this.base, val));
 
             // Fix #4158
@@ -34294,6 +34952,12 @@ var LogScale = Scale.extend({
     },
 
     /**
+     * @param {number} splitNumber
+     * @return {Array.<Array.<number>>}
+     */
+    getMinorTicks: intervalScaleProto$1.getMinorTicks,
+
+    /**
      * @param {number} val
      * @return {string}
      */
@@ -36156,7 +36820,7 @@ Axis.prototype = {
      * @return {boolean}
      */
     containData: function (data) {
-        return this.contain(this.dataToCoord(data));
+        return this.scale.contain(data);
     },
 
     /**
@@ -36244,7 +36908,7 @@ Axis.prototype = {
      * `axis.getTicksCoords` considers `onBand`, which is used by
      * `boundaryGap:true` of category axis and splitLine and splitArea.
      * @param {Object} [opt]
-     * @param {number} [opt.tickModel=axis.model.getModel('axisTick')]
+     * @param {Model} [opt.tickModel=axis.model.getModel('axisTick')]
      * @param {boolean} [opt.clamp] If `true`, the first and the last
      *        tick must be at the axis end points. Otherwise, clip ticks
      *        that outside the axis extent.
@@ -36277,6 +36941,33 @@ Axis.prototype = {
     },
 
     /**
+     * @return {Array.<Array.<Object>>} [{ coord: ..., tickValue: ...}]
+     */
+    getMinorTicksCoords: function () {
+        if (this.scale.type === 'ordinal') {
+            // Category axis doesn't support minor ticks
+            return [];
+        }
+
+        var minorTickModel = this.model.getModel('minorTick');
+        var splitNumber = minorTickModel.get('splitNumber');
+        // Protection.
+        if (!(splitNumber > 0 && splitNumber < 100)) {
+            splitNumber = 5;
+        }
+        var minorTicks = this.scale.getMinorTicks(splitNumber);
+        var minorTicksCoords = map(minorTicks, function (minorTicksGroup) {
+            return map(minorTicksGroup, function (minorTick) {
+                return {
+                    coord: this.dataToCoord(minorTick),
+                    tickValue: minorTick
+                };
+            }, this);
+        }, this);
+        return minorTicksCoords;
+    },
+
+    /**
      * @return {Array.<Object>} [{
      *     formattedLabel: string,
      *     rawLabel: axis.scale.getLabel(tickValue)
@@ -36518,7 +37209,7 @@ SeriesModel.extend({
                 throw new Error('Line not support coordinateSystem besides cartesian and polar');
             }
         }
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     defaultOption: {
@@ -38362,7 +39053,7 @@ Chart.extend({
         // FIXME step not support polar
         var step = !isCoordSysPolar && seriesModel.get('step');
         var clipShapeForSymbol;
-        if (coordSys && coordSys.getArea) {
+        if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) {
             clipShapeForSymbol = coordSys.getArea();
             // Avoid float number rounding error for symbol on the edge of axis extent.
             // See #7913 and `test/dataZoom-clip.html`.
@@ -38377,6 +39068,7 @@ Chart.extend({
                 clipShapeForSymbol.r1 += 0.5;
             }
         }
+        this._clipShapeForSymbol = clipShapeForSymbol;
         // Initialization animation or coordinate system changed
         if (
             !(polyline && prevCoordSys.type === coordSys.type && step === this._step)
@@ -38529,6 +39221,10 @@ Chart.extend({
                     // Null data
                     return;
                 }
+                // fix #11360: should't draw symbol outside clipShapeForSymbol
+                if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(pt[0], pt[1])) {
+                    return;
+                }
                 symbol = new SymbolClz$1(data, dataIndex);
                 symbol.position = pt;
                 symbol.setZ(
@@ -39584,7 +40280,7 @@ axisDefault.valueAxis = merge({
     // scale: false,
 
     // AxisTick and axisLabel and splitLine are caculated based on splitNumber.
-    splitNumber: 5
+    splitNumber: 5,
 
     // Interval specifies the span of the ticks is mandatorily.
     // interval: null
@@ -39595,6 +40291,30 @@ axisDefault.valueAxis = merge({
     // Specify max interval when auto calculate tick interval.
     // maxInterval: null
 
+    minorTick: {
+        // Minor tick, not available for cateogry axis.
+        show: false,
+        // Split number of minor ticks. The value should be in range of (0, 100)
+        splitNumber: 5,
+        // Lenght of minor tick
+        length: 3,
+
+        // Same inside with axisTick
+
+        // Line style
+        lineStyle: {
+            // Default to be same with axisTick
+        }
+    },
+
+    minorSplitLine: {
+        show: false,
+
+        lineStyle: {
+            color: '#eee',
+            width: 1
+        }
+    }
 }, defaultOption);
 
 axisDefault.timeAxis = defaults({
@@ -40711,10 +41431,12 @@ var builders = {
         var axisModel = this.axisModel;
         var opt = this.opt;
 
-        var tickEls = buildAxisTick(this, axisModel, opt);
+        var ticksEls = buildAxisMajorTicks(this, axisModel, opt);
         var labelEls = buildAxisLabel(this, axisModel, opt);
 
-        fixMinMaxLabelShow(axisModel, labelEls, tickEls);
+        fixMinMaxLabelShow(axisModel, labelEls, ticksEls);
+
+        buildAxisMinorTicks(this, axisModel, opt);
     },
 
     /**
@@ -41033,42 +41755,27 @@ function isNameLocationCenter(nameLocation) {
     return nameLocation === 'middle' || nameLocation === 'center';
 }
 
-function buildAxisTick(axisBuilder, axisModel, opt) {
-    var axis = axisModel.axis;
-
-    if (!axisModel.get('axisTick.show') || axis.scale.isBlank()) {
-        return;
-    }
-
-    var tickModel = axisModel.getModel('axisTick');
-
-    var lineStyleModel = tickModel.getModel('lineStyle');
-    var tickLen = tickModel.get('length');
-
-    var ticksCoords = axis.getTicksCoords();
 
+function createTicks(ticksCoords, tickTransform, tickEndCoord, tickLineStyle, aniid) {
+    var tickEls = [];
     var pt1 = [];
     var pt2 = [];
-    var matrix = axisBuilder._transform;
-
-    var tickEls = [];
-
     for (var i = 0; i < ticksCoords.length; i++) {
         var tickCoord = ticksCoords[i].coord;
 
         pt1[0] = tickCoord;
         pt1[1] = 0;
         pt2[0] = tickCoord;
-        pt2[1] = opt.tickDirection * tickLen;
+        pt2[1] = tickEndCoord;
 
-        if (matrix) {
-            applyTransform(pt1, pt1, matrix);
-            applyTransform(pt2, pt2, matrix);
+        if (tickTransform) {
+            applyTransform(pt1, pt1, tickTransform);
+            applyTransform(pt2, pt2, tickTransform);
         }
         // Tick line, Not use group transform to have better line draw
         var tickEl = new Line({
             // Id for animation
-            anid: 'tick_' + ticksCoords[i].tickValue,
+            anid: aniid + '_' + ticksCoords[i].tickValue,
             subPixelOptimize: true,
             shape: {
                 x1: pt1[0],
@@ -41076,22 +41783,80 @@ function buildAxisTick(axisBuilder, axisModel, opt) {
                 x2: pt2[0],
                 y2: pt2[1]
             },
-            style: defaults(
-                lineStyleModel.getLineStyle(),
-                {
-                    stroke: axisModel.get('axisLine.lineStyle.color')
-                }
-            ),
+            style: tickLineStyle,
             z2: 2,
             silent: true
         });
-        axisBuilder.group.add(tickEl);
         tickEls.push(tickEl);
     }
-
     return tickEls;
 }
 
+function buildAxisMajorTicks(axisBuilder, axisModel, opt) {
+    var axis = axisModel.axis;
+
+    var tickModel = axisModel.getModel('axisTick');
+
+    if (!tickModel.get('show') || axis.scale.isBlank()) {
+        return;
+    }
+
+    var lineStyleModel = tickModel.getModel('lineStyle');
+    var tickEndCoord = opt.tickDirection * tickModel.get('length');
+
+    var ticksCoords = axis.getTicksCoords();
+
+    var ticksEls = createTicks(ticksCoords, axisBuilder._transform, tickEndCoord, defaults(
+        lineStyleModel.getLineStyle(),
+        {
+            stroke: axisModel.get('axisLine.lineStyle.color')
+        }
+    ), 'ticks');
+
+    for (var i = 0; i < ticksEls.length; i++) {
+        axisBuilder.group.add(ticksEls[i]);
+    }
+
+    return ticksEls;
+}
+
+function buildAxisMinorTicks(axisBuilder, axisModel, opt) {
+    var axis = axisModel.axis;
+
+    var minorTickModel = axisModel.getModel('minorTick');
+
+    if (!minorTickModel.get('show') || axis.scale.isBlank()) {
+        return;
+    }
+
+    var minorTicksCoords = axis.getMinorTicksCoords();
+    if (!minorTicksCoords.length) {
+        return;
+    }
+
+    var lineStyleModel = minorTickModel.getModel('lineStyle');
+    var tickEndCoord = opt.tickDirection * minorTickModel.get('length');
+
+    var minorTickLineStyle = defaults(
+        lineStyleModel.getLineStyle(),
+        defaults(
+            axisModel.getModel('axisTick').getLineStyle(),
+            {
+                stroke: axisModel.get('axisLine.lineStyle.color')
+            }
+        )
+    );
+
+    for (var i = 0; i < minorTicksCoords.length; i++) {
+        var minorTicksEls = createTicks(
+            minorTicksCoords[i], axisBuilder._transform, tickEndCoord, minorTickLineStyle, 'minorticks_' + i
+        );
+        for (var k = 0; k < minorTicksEls.length; k++) {
+            axisBuilder.group.add(minorTicksEls[k]);
+        }
+    }
+}
+
 function buildAxisLabel(axisBuilder, axisModel, opt) {
     var axis = axisModel.axis;
     var show = retrieve(opt.axisLabelShow, axisModel.get('axisLabel.show'));
@@ -41760,17 +42525,9 @@ var axisBuilderAttrs = [
     'axisLine', 'axisTickLabel', 'axisName'
 ];
 var selfBuilderAttrs = [
-    'splitArea', 'splitLine'
+    'splitArea', 'splitLine', 'minorSplitLine'
 ];
 
-// function getAlignWithLabel(model, axisModel) {
-//     var alignWithLabel = model.get('alignWithLabel');
-//     if (alignWithLabel === 'auto') {
-//         alignWithLabel = axisModel.get('axisTick.alignWithLabel');
-//     }
-//     return alignWithLabel;
-// }
-
 var CartesianAxisView = AxisView.extend({
 
     type: 'cartesianAxis',
@@ -41848,8 +42605,6 @@ var CartesianAxisView = AxisView.extend({
         var p1 = [];
         var p2 = [];
 
-        // Simple optimization
-        // Batching the lines if color are the same
         var lineStyle = lineStyleModel.getLineStyle();
         for (var i = 0; i < ticksCoords.length; i++) {
             var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord);
@@ -41891,6 +42646,63 @@ var CartesianAxisView = AxisView.extend({
      * @param {module:echarts/coord/cartesian/GridModel} gridModel
      * @private
      */
+    _minorSplitLine: function (axisModel, gridModel) {
+        var axis = axisModel.axis;
+
+        var minorSplitLineModel = axisModel.getModel('minorSplitLine');
+        var lineStyleModel = minorSplitLineModel.getModel('lineStyle');
+
+        var gridRect = gridModel.coordinateSystem.getRect();
+        var isHorizontal = axis.isHorizontal();
+
+        var minorTicksCoords = axis.getMinorTicksCoords();
+        if (!minorTicksCoords.length) {
+            return;
+        }
+        var p1 = [];
+        var p2 = [];
+
+        var lineStyle = lineStyleModel.getLineStyle();
+
+
+        for (var i = 0; i < minorTicksCoords.length; i++) {
+            for (var k = 0; k < minorTicksCoords[i].length; k++) {
+                var tickCoord = axis.toGlobalCoord(minorTicksCoords[i][k].coord);
+
+                if (isHorizontal) {
+                    p1[0] = tickCoord;
+                    p1[1] = gridRect.y;
+                    p2[0] = tickCoord;
+                    p2[1] = gridRect.y + gridRect.height;
+                }
+                else {
+                    p1[0] = gridRect.x;
+                    p1[1] = tickCoord;
+                    p2[0] = gridRect.x + gridRect.width;
+                    p2[1] = tickCoord;
+                }
+
+                this._axisGroup.add(new Line({
+                    anid: 'minor_line_' + minorTicksCoords[i][k].tickValue,
+                    subPixelOptimize: true,
+                    shape: {
+                        x1: p1[0],
+                        y1: p1[1],
+                        x2: p2[0],
+                        y2: p2[1]
+                    },
+                    style: lineStyle,
+                    silent: true
+                }));
+            }
+        }
+    },
+
+    /**
+     * @param {module:echarts/coord/cartesian/AxisModel} axisModel
+     * @param {module:echarts/coord/cartesian/GridModel} gridModel
+     * @private
+     */
     _splitArea: function (axisModel, gridModel) {
         var axis = axisModel.axis;
 
@@ -42106,7 +42918,7 @@ var BaseBarSeries = SeriesModel.extend({
     type: 'series.__base_bar__',
 
     getInitialData: function (option, ecModel) {
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     getMarkerPosition: function (value) {
@@ -42810,11 +43622,18 @@ var getLayout = {
     }
 };
 
+function isZeroOnPolar(layout) {
+    return layout.startAngle != null
+        && layout.endAngle != null
+        && layout.startAngle === layout.endAngle;
+}
+
 function updateStyle(
     el, data, dataIndex, itemModel, layout, seriesModel, isHorizontal, isPolar
 ) {
     var color = data.getItemVisual(dataIndex, 'color');
     var opacity = data.getItemVisual(dataIndex, 'opacity');
+    var stroke = data.getVisual('borderColor');
     var itemStyleModel = itemModel.getModel('itemStyle');
     var hoverStyle = itemModel.getModel('emphasis.itemStyle').getBarItemStyle();
 
@@ -42824,7 +43643,8 @@ function updateStyle(
 
     el.useStyle(defaults(
         {
-            fill: color,
+            stroke: isZeroOnPolar(layout) ? 'none' : stroke,
+            fill: isZeroOnPolar(layout) ? 'none' : color,
             opacity: opacity
         },
         itemStyleModel.getBarItemStyle()
@@ -42843,7 +43663,9 @@ function updateStyle(
             seriesModel, dataIndex, labelPositionOutside
         );
     }
-
+    if (isZeroOnPolar(layout)) {
+        hoverStyle.fill = hoverStyle.stroke = 'none';
+    }
     setHoverStyle(el, hoverStyle);
 }
 
@@ -43164,6 +43986,60 @@ var selectableMixin = {
 * under the License.
 */
 
+
+/**
+ * LegendVisualProvider is an bridge that pick encoded color from data and
+ * provide to the legend component.
+ * @param {Function} getDataWithEncodedVisual Function to get data after filtered. It stores all the encoding info
+ * @param {Function} getRawData Function to get raw data before filtered.
+ */
+function LegendVisualProvider(getDataWithEncodedVisual, getRawData) {
+    this.getAllNames = function () {
+        var rawData = getRawData();
+        // We find the name from the raw data. In case it's filtered by the legend component.
+        // Normally, the name can be found in rawData, but can't be found in filtered data will display as gray.
+        return rawData.mapArray(rawData.getName);
+    };
+
+    this.containName = function (name) {
+        var rawData = getRawData();
+        return rawData.indexOfName(name) >= 0;
+    };
+
+    this.indexOfName = function (name) {
+        // Only get data when necessary.
+        // Because LegendVisualProvider constructor may be new in the stage that data is not prepared yet.
+        // Invoking Series#getData immediately will throw an error.
+        var dataWithEncodedVisual = getDataWithEncodedVisual();
+        return dataWithEncodedVisual.indexOfName(name);
+    };
+
+    this.getItemVisual = function (dataIndex, key) {
+        // Get encoded visual properties from final filtered data.
+        var dataWithEncodedVisual = getDataWithEncodedVisual();
+        return dataWithEncodedVisual.getItemVisual(dataIndex, key);
+    };
+}
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
 var PieSeries = extendSeriesModel({
 
     type: 'series.pie',
@@ -43174,9 +44050,9 @@ var PieSeries = extendSeriesModel({
 
         // Enable legend selection for each data item
         // Use a function instead of direct access because data reference may changed
-        this.legendDataProvider = function () {
-            return this.getRawData();
-        };
+        this.legendVisualProvider = new LegendVisualProvider(
+            bind(this.getData, this), bind(this.getRawData, this)
+        );
 
         this.updateSelectedMap(this._createSelectableList());
 
@@ -43191,7 +44067,10 @@ var PieSeries = extendSeriesModel({
     },
 
     getInitialData: function (option, ecModel) {
-        return createListSimply(this, ['value']);
+        return createListSimply(this, {
+            coordDimensions: ['value'],
+            encodeDefaulter: curry(makeSeriesEncodeForNameBased, this)
+        });
     },
 
     _createSelectableList: function () {
@@ -43280,12 +44159,28 @@ var PieSeries = extendSeriesModel({
 
         // cursor: null,
 
+        left: 0,
+        top: 0,
+        right: 0,
+        bottom: 0,
+        width: null,
+        height: null,
+
         label: {
             // If rotate around circle
             rotate: false,
             show: true,
             // 'outer', 'inside', 'center'
-            position: 'outer'
+            position: 'outer',
+            // 'none', 'labelLine', 'edge'. Works only when position is 'outer'
+            alignTo: 'none',
+            // Closest distance between label and chart edge.
+            // Works only position is 'outer' and alignTo is 'edge'.
+            margin: '25%',
+            // Works only position is 'outer' and alignTo is not 'edge'.
+            bleedMargin: 10,
+            // Distance between text and label line.
+            distanceToLabelLine: 5
             // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
             // 默认使用全局文本样式,详见TEXTSTYLE
             // distance: 当position为inner时有效,为label位置到圆心的距离与圆半径(环状图为内外半径和)的比例系数
@@ -43591,7 +44486,7 @@ piePieceProto._updateLabel = function (data, idx, withAnimation) {
         {
             labelFetcher: data.hostModel,
             labelDataIndex: idx,
-            defaultText: data.getName(idx),
+            defaultText: labelLayout.text,
             autoColor: visualColor,
             useInsideStyle: !!labelLayout.inside
         },
@@ -43887,33 +44782,20 @@ var dataColor = function (seriesType) {
                             dataAll.getName(rawIdx) || (rawIdx + ''), seriesModel.__paletteScope,
                             dataAll.count()
                         );
-                    // Legend may use the visual info in data before processed
-                    dataAll.setItemVisual(rawIdx, 'color', color);
-
                     // Data is not filtered
                     if (filteredIdx != null) {
                         data.setItemVisual(filteredIdx, 'color', color);
                     }
                 }
-                else {
-                    // Set data all color for legend
-                    dataAll.setItemVisual(rawIdx, 'color', singleDataColor);
-                }
 
                 if (!singleDataBorderColor) {
                     var borderColor = itemModel.get('itemStyle.borderColor');
-                    // Legend may use the visual info in data before processed
-                    dataAll.setItemVisual(rawIdx, 'borderColor', borderColor);
 
                     // Data is not filtered
                     if (filteredIdx != null) {
                         data.setItemVisual(filteredIdx, 'borderColor', borderColor);
                     }
                 }
-                else {
-                    // Set data all borderColor for legend
-                    dataAll.setItemVisual(rawIdx, 'borderColor', singleDataBorderColor);
-                }
             });
         }
     };
@@ -43942,13 +44824,17 @@ var dataColor = function (seriesType) {
 
 var RADIAN$1 = Math.PI / 180;
 
-function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
+function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) {
     list.sort(function (a, b) {
         return a.y - b.y;
     });
 
     function shiftDown(start, end, delta, dir) {
         for (var j = start; j < end; j++) {
+            if (list[j].y + delta > viewTop + viewHeight) {
+                break;
+            }
+
             list[j].y += delta;
             if (j > start
                 && j + 1 < end
@@ -43964,6 +44850,10 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
 
     function shiftUp(end, delta) {
         for (var j = end; j >= 0; j--) {
+            if (list[j].y - delta < viewTop) {
+                break;
+            }
+
             list[j].y -= delta;
             if (j > 0
                 && list[j].y > list[j - 1].y + list[j - 1].height
@@ -43983,6 +44873,10 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
                 : 0;                    // up
 
         for (var i = 0, l = list.length; i < l; i++) {
+            if (list[i].labelAlignTo !== 'none') {
+                continue;
+            }
+
             var deltaY = Math.abs(list[i].y - cy);
             var length = list[i].len;
             var length2 = list[i].len2;
@@ -44012,6 +44906,12 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
     var upList = [];
     var downList = [];
     for (var i = 0; i < len; i++) {
+        if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
+            var dx = list[i].x - farthestX;
+            list[i].linePoints[1][0] += dx;
+            list[i].x = farthestX;
+        }
+
         delta = list[i].y - lastY;
         if (delta < 0) {
             shiftDown(i, len, -delta, dir);
@@ -44033,39 +44933,85 @@ function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
     changeX(downList, true, cx, cy, r, dir);
 }
 
-function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight) {
+function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) {
     var leftList = [];
     var rightList = [];
+    var leftmostX = Number.MAX_VALUE;
+    var rightmostX = -Number.MAX_VALUE;
     for (var i = 0; i < labelLayoutList.length; i++) {
         if (isPositionCenter(labelLayoutList[i])) {
             continue;
         }
         if (labelLayoutList[i].x < cx) {
+            leftmostX = Math.min(leftmostX, labelLayoutList[i].x);
             leftList.push(labelLayoutList[i]);
         }
         else {
+            rightmostX = Math.max(rightmostX, labelLayoutList[i].x);
             rightList.push(labelLayoutList[i]);
         }
     }
 
-    adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight);
-    adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight);
+    adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX);
+    adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX);
 
     for (var i = 0; i < labelLayoutList.length; i++) {
-        if (isPositionCenter(labelLayoutList[i])) {
+        var layout = labelLayoutList[i];
+        if (isPositionCenter(layout)) {
             continue;
         }
-        var linePoints = labelLayoutList[i].linePoints;
+
+        var linePoints = layout.linePoints;
         if (linePoints) {
+            var isAlignToEdge = layout.labelAlignTo === 'edge';
+
+            var realTextWidth = layout.textRect.width;
+            var targetTextWidth;
+            if (isAlignToEdge) {
+                if (layout.x < cx) {
+                    targetTextWidth = linePoints[2][0] - layout.labelDistance
+                            - viewLeft - layout.labelMargin;
+                }
+                else {
+                    targetTextWidth = viewLeft + viewWidth - layout.labelMargin
+                            - linePoints[2][0] - layout.labelDistance;
+                }
+            }
+            else {
+                if (layout.x < cx) {
+                    targetTextWidth = layout.x - viewLeft - layout.bleedMargin;
+                }
+                else {
+                    targetTextWidth = viewLeft + viewWidth - layout.x - layout.bleedMargin;
+                }
+            }
+            if (targetTextWidth < layout.textRect.width) {
+                layout.text = truncateText(layout.text, targetTextWidth, layout.font);
+                if (layout.labelAlignTo === 'edge') {
+                    realTextWidth = getWidth(layout.text, layout.font);
+                }
+            }
+
             var dist = linePoints[1][0] - linePoints[2][0];
-            if (labelLayoutList[i].x < cx) {
-                linePoints[2][0] = labelLayoutList[i].x + 3;
+            if (isAlignToEdge) {
+                if (layout.x < cx) {
+                    linePoints[2][0] = viewLeft + layout.labelMargin + realTextWidth + layout.labelDistance;
+                }
+                else {
+                    linePoints[2][0] = viewLeft + viewWidth - layout.labelMargin
+                            - realTextWidth - layout.labelDistance;
+                }
             }
             else {
-                linePoints[2][0] = labelLayoutList[i].x - 3;
+                if (layout.x < cx) {
+                    linePoints[2][0] = layout.x + layout.labelDistance;
+                }
+                else {
+                    linePoints[2][0] = layout.x - layout.labelDistance;
+                }
+                linePoints[1][0] = linePoints[2][0] + dist;
             }
-            linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y;
-            linePoints[1][0] = linePoints[2][0] + dist;
+            linePoints[1][1] = linePoints[2][1] = layout.y;
         }
     }
 }
@@ -44075,7 +45021,7 @@ function isPositionCenter(layout) {
     return layout.position === 'center';
 }
 
-var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
+var labelLayout = function (seriesModel, r, viewWidth, viewHeight, viewLeft, viewTop) {
     var data = seriesModel.getData();
     var labelLayoutList = [];
     var cx;
@@ -44090,10 +45036,17 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
         var labelModel = itemModel.getModel('label');
         // Use position in normal or emphasis
         var labelPosition = labelModel.get('position') || itemModel.get('emphasis.label.position');
+        var labelDistance = labelModel.get('distanceToLabelLine');
+        var labelAlignTo = labelModel.get('alignTo');
+        var labelMargin = parsePercent$1(labelModel.get('margin'), viewWidth);
+        var bleedMargin = labelModel.get('bleedMargin');
+        var font = labelModel.getFont();
 
         var labelLineModel = itemModel.getModel('labelLine');
         var labelLineLen = labelLineModel.get('length');
+        labelLineLen = parsePercent$1(labelLineLen, viewWidth);
         var labelLineLen2 = labelLineModel.get('length2');
+        labelLineLen2 = parsePercent$1(labelLineLen2, viewWidth);
 
         if (layout.angle < minShowLabelRadian) {
             return;
@@ -44111,6 +45064,12 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
         cx = layout.cx;
         cy = layout.cy;
 
+        var text = seriesModel.getFormattedLabel(idx, 'normal')
+                || data.getName(idx);
+        var textRect = getBoundingRect(
+            text, font, textAlign, 'top'
+        );
+
         var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
         if (labelPosition === 'center') {
             textX = layout.cx;
@@ -44131,14 +45090,25 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
                 var x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2);
                 var y3 = y2;
 
-                textX = x3 + (dx < 0 ? -5 : 5);
+                if (labelAlignTo === 'edge') {
+                    // Adjust textX because text align of edge is opposite
+                    textX = dx < 0
+                        ? viewLeft + labelMargin
+                        : viewLeft + viewWidth - labelMargin;
+                }
+                else {
+                    textX = x3 + (dx < 0 ? -labelDistance : labelDistance);
+                }
                 textY = y3;
                 linePoints = [[x1, y1], [x2, y2], [x3, y3]];
             }
 
-            textAlign = isLabelInside ? 'center' : (dx > 0 ? 'left' : 'right');
+            textAlign = isLabelInside
+                ? 'center'
+                : (labelAlignTo === 'edge'
+                    ? (dx > 0 ? 'right' : 'left')
+                    : (dx > 0 ? 'left' : 'right'));
         }
-        var font = labelModel.getFont();
 
         var labelRotate;
         var rotate = labelModel.get('rotate');
@@ -44150,11 +45120,7 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
                 ? (dx < 0 ? -midAngle + Math.PI : -midAngle)
                 : 0;
         }
-        var text = seriesModel.getFormattedLabel(idx, 'normal')
-                    || data.getName(idx);
-        var textRect = getBoundingRect(
-            text, font, textAlign, 'top'
-        );
+
         hasLabelRotate = !!labelRotate;
         layout.label = {
             x: textX,
@@ -44167,7 +45133,14 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
             textAlign: textAlign,
             verticalAlign: 'middle',
             rotation: labelRotate,
-            inside: isLabelInside
+            inside: isLabelInside,
+            labelDistance: labelDistance,
+            labelAlignTo: labelAlignTo,
+            labelMargin:labelMargin,
+            bleedMargin: bleedMargin,
+            textRect: textRect,
+            text: text,
+            font: font
         };
 
         // Not layout the inside label
@@ -44176,7 +45149,7 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
         }
     });
     if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
-        avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight);
+        avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop);
     }
 };
 
@@ -44203,10 +45176,20 @@ var labelLayout = function (seriesModel, r, viewWidth, viewHeight, sum) {
 var PI2$4 = Math.PI * 2;
 var RADIAN = Math.PI / 180;
 
+function getViewRect(seriesModel, api) {
+    return getLayoutRect(
+        seriesModel.getBoxLayoutParams(), {
+            width: api.getWidth(),
+            height: api.getHeight()
+        }
+    );
+}
+
 var pieLayout = function (seriesType, ecModel, api, payload) {
     ecModel.eachSeriesByType(seriesType, function (seriesModel) {
         var data = seriesModel.getData();
         var valueDim = data.mapDimension('value');
+        var viewRect = getViewRect(seriesModel, api);
 
         var center = seriesModel.get('center');
         var radius = seriesModel.get('radius');
@@ -44218,11 +45201,11 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
             center = [center, center];
         }
 
-        var width = api.getWidth();
-        var height = api.getHeight();
+        var width = parsePercent$1(viewRect.width, api.getWidth());
+        var height = parsePercent$1(viewRect.height, api.getHeight());
         var size = Math.min(width, height);
-        var cx = parsePercent$1(center[0], width);
-        var cy = parsePercent$1(center[1], height);
+        var cx = parsePercent$1(center[0], width) + viewRect.x;
+        var cy = parsePercent$1(center[1], height) + viewRect.y;
         var r0 = parsePercent$1(radius[0], size / 2);
         var r = parsePercent$1(radius[1], size / 2);
 
@@ -44268,7 +45251,8 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
                     r0: r0,
                     r: roseType
                         ? NaN
-                        : r
+                        : r,
+                    viewRect: viewRect
                 });
                 return;
             }
@@ -44301,7 +45285,8 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
                 r0: r0,
                 r: roseType
                     ? linearMap(value, extent, [r0, r])
-                    : r
+                    : r,
+                viewRect: viewRect
             });
 
             currentAngle = endAngle;
@@ -44339,7 +45324,7 @@ var pieLayout = function (seriesType, ecModel, api, payload) {
             }
         }
 
-        labelLayout(seriesModel, r, width, height);
+        labelLayout(seriesModel, r, viewRect.width, viewRect.height, viewRect.x, viewRect.y);
     });
 };
 
@@ -44450,7 +45435,7 @@ SeriesModel.extend({
     dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'],
 
     getInitialData: function (option, ecModel) {
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     brushSelector: 'point',
@@ -45658,9 +46643,10 @@ var RadarSeries = SeriesModel.extend({
 
         // Enable legend selection for each data item
         // Use a function instead of direct access because data reference may changed
-        this.legendDataProvider = function () {
-            return this.getRawData();
-        };
+        this.legendVisualProvider = new LegendVisualProvider(
+            bind(this.getData, this), bind(this.getRawData, this)
+        );
+
     },
 
     getInitialData: function (option, ecModel) {
@@ -46638,7 +47624,10 @@ var MapSeries = SeriesModel.extend({
     seriesGroup: [],
 
     getInitialData: function (option) {
-        var data = createListSimply(this, ['value']);
+        var data = createListSimply(this, {
+            coordDimensions: ['value'],
+            encodeDefaulter: curry(makeSeriesEncodeForNameBased, this)
+        });
         var valueDim = data.mapDimension('value');
         var dataNameMap = createHashMap();
         var selectTargetList = [];
@@ -49720,7 +50709,7 @@ Tree.prototype = {
  * @param {Array.<Object>} treeOptions.leaves
  * @return module:echarts/data/Tree
  */
-Tree.createTree = function (dataRoot, hostModel, treeOptions) {
+Tree.createTree = function (dataRoot, hostModel, treeOptions, beforeLink) {
 
     var tree = new Tree(hostModel, treeOptions.levels, treeOptions.leaves);
     var listData = [];
@@ -49767,6 +50756,8 @@ Tree.createTree = function (dataRoot, hostModel, treeOptions) {
 
     tree.update();
 
+    beforeLink && beforeLink(list);
+
     return tree;
 };
 
@@ -49831,7 +50822,18 @@ SeriesModel.extend({
 
         treeOption.leaves = leaves;
 
-        var tree = Tree.createTree(root, this, treeOption);
+        var tree = Tree.createTree(root, this, treeOption, beforeLink);
+
+        function beforeLink(nodeData) {
+            nodeData.wrapMethod('getItemModel', function (model, idx) {
+                var node = tree.getNodeByDataIndex(idx);
+                var leavesModel = node.getLeavesModel();
+                if (!node.children.length || !node.isExpand) {
+                    model.parentModel = leavesModel;
+                }
+                return model;
+            });
+        }
 
         var treeDepth = 0;
 
@@ -50128,7 +51130,7 @@ function radialCoordinate(x, y) {
  * @param {module:echarts/ExtensionAPI} api  provide the API list that the developer can call
  * @return {module:zrender/core/BoundingRect}  size of rect to draw the sankey view
  */
-function getViewRect(seriesModel, api) {
+function getViewRect$1(seriesModel, api) {
     return getLayoutRect(
         seriesModel.getBoxLayoutParams(), {
             width: api.getWidth(),
@@ -50941,7 +51943,7 @@ var treeLayout = function (ecModel, api) {
 };
 
 function commonLayout(seriesModel, api) {
-    var layoutInfo = getViewRect(seriesModel, api);
+    var layoutInfo = getViewRect$1(seriesModel, api);
     seriesModel.layoutInfo = layoutInfo;
     var layout = seriesModel.get('layout');
     var width = 0;
@@ -54907,10 +55909,14 @@ var GraphSeries = extendSeriesModel({
     init: function (option) {
         GraphSeries.superApply(this, 'init', arguments);
 
+        var self = this;
+        function getCategoriesData() {
+            return self._categoriesData;
+        }
         // Provide data for legend select
-        this.legendDataProvider = function () {
-            return this._categoriesData;
-        };
+        this.legendVisualProvider = new LegendVisualProvider(
+            getCategoriesData, getCategoriesData
+        );
 
         this.fillDataTextStyle(option.edges || option.links);
 
@@ -56100,6 +57106,7 @@ extendChartView({
     },
 
     render: function (seriesModel, ecModel, api) {
+        var graphView = this;
         var coordSys = seriesModel.coordinateSystem;
 
         this._model = seriesModel;
@@ -56169,6 +57176,7 @@ extendChartView({
 
             if (itemModel.get('focusNodeAdjacency')) {
                 el.on('mouseover', el[FOCUS_ADJACENCY] = function () {
+                    graphView._clearTimer();
                     api.dispatchAction({
                         type: 'focusNodeAdjacency',
                         seriesId: seriesModel.id,
@@ -56176,10 +57184,7 @@ extendChartView({
                     });
                 });
                 el.on('mouseout', el[UNFOCUS_ADJACENCY] = function () {
-                    api.dispatchAction({
-                        type: 'unfocusNodeAdjacency',
-                        seriesId: seriesModel.id
-                    });
+                    graphView._dispatchUnfocus(api);
                 });
             }
 
@@ -56193,6 +57198,7 @@ extendChartView({
 
             if (edge.getModel().get('focusNodeAdjacency')) {
                 el.on('mouseover', el[FOCUS_ADJACENCY] = function () {
+                    graphView._clearTimer();
                     api.dispatchAction({
                         type: 'focusNodeAdjacency',
                         seriesId: seriesModel.id,
@@ -56200,10 +57206,7 @@ extendChartView({
                     });
                 });
                 el.on('mouseout', el[UNFOCUS_ADJACENCY] = function () {
-                    api.dispatchAction({
-                        type: 'unfocusNodeAdjacency',
-                        seriesId: seriesModel.id
-                    });
+                    graphView._dispatchUnfocus(api);
                 });
             }
         });
@@ -56255,6 +57258,27 @@ extendChartView({
     dispose: function () {
         this._controller && this._controller.dispose();
         this._controllerHost = {};
+        this._clearTimer();
+    },
+
+    _dispatchUnfocus: function (api, opt) {
+        var self = this;
+        this._clearTimer();
+        this._unfocusDelayTimer = setTimeout(function () {
+            self._unfocusDelayTimer = null;
+            api.dispatchAction({
+                type: 'unfocusNodeAdjacency',
+                seriesId: self._model.id
+            });
+        }, 500);
+
+    },
+
+    _clearTimer: function () {
+        if (this._unfocusDelayTimer) {
+            clearTimeout(this._unfocusDelayTimer);
+            this._unfocusDelayTimer = null;
+        }
     },
 
     focusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
@@ -57296,7 +58320,7 @@ var forceLayout = function (ecModel) {
 */
 
 // FIXME Where to create the simple view coordinate system
-function getViewRect$1(seriesModel, api, aspect) {
+function getViewRect$2(seriesModel, api, aspect) {
     var option = seriesModel.getBoxLayoutParams();
     option.aspect = aspect;
     return getLayoutRect(option, {
@@ -57333,7 +58357,7 @@ var createView = function (ecModel, api) {
             }
             var aspect = (max[0] - min[0]) / (max[1] - min[1]);
             // FIXME If get view rect after data processed?
-            var viewRect = getViewRect$1(seriesModel, api, aspect);
+            var viewRect = getViewRect$2(seriesModel, api, aspect);
             // Position may be NaN, use view rect instead
             if (isNaN(aspect)) {
                 min = [viewRect.x, viewRect.y];
@@ -58054,15 +59078,18 @@ var FunnelSeries = extendSeriesModel({
 
         // Enable legend selection for each data item
         // Use a function instead of direct access because data reference may changed
-        this.legendDataProvider = function () {
-            return this.getRawData();
-        };
+        this.legendVisualProvider = new LegendVisualProvider(
+            bind(this.getData, this), bind(this.getRawData, this)
+        );
         // Extend labelLine emphasis
         this._defaultLabelLine(option);
     },
 
     getInitialData: function (option, ecModel) {
-        return createListSimply(this, ['value']);
+        return createListSimply(this, {
+            coordDimensions: ['value'],
+            encodeDefaulter: curry(makeSeriesEncodeForNameBased, this)
+        });
     },
 
     _defaultLabelLine: function (option) {
@@ -58374,7 +59401,7 @@ var FunnelView = Chart.extend({
 * under the License.
 */
 
-function getViewRect$2(seriesModel, api) {
+function getViewRect$3(seriesModel, api) {
     return getLayoutRect(
         seriesModel.getBoxLayoutParams(), {
             width: api.getWidth(),
@@ -58529,7 +59556,7 @@ var funnelLayout = function (ecModel, api, payload) {
         var data = seriesModel.getData();
         var valueDim = data.mapDimension('value');
         var sort = seriesModel.get('sort');
-        var viewRect = getViewRect$2(seriesModel, api);
+        var viewRect = getViewRect$3(seriesModel, api);
         var indices = getSortedIndices(data, sort);
 
         var sizeExtent = [
@@ -59931,12 +60958,6 @@ function BrushController(zr) {
 
     /**
      * @private
-     * @type {Object}
-     */
-    this._lastMouseMovePoint = {};
-
-    /**
-     * @private
      * @type {Array}
      */
     this._covers = [];
@@ -59979,7 +61000,8 @@ function BrushController(zr) {
      * @type {Object}
      */
     this._handlers = {};
-    each$12(mouseHandlers, function (handler, eventName) {
+
+    each$12(pointerHandlers, function (handler, eventName) {
         this._handlers[eventName] = bind(handler, this);
     }, this);
 }
@@ -60175,9 +61197,7 @@ function doEnableBrush(controller, brushOption) {
         take(zr, MUTEX_RESOURCE_KEY, controller._uid);
     }
 
-    each$12(controller._handlers, function (handler, eventName) {
-        zr.on(eventName, handler);
-    });
+    mountHandlers(zr, controller._handlers);
 
     controller._brushType = brushOption.brushType;
     controller._brushOption = merge(clone(DEFAULT_BRUSH_OPT), brushOption, true);
@@ -60188,13 +61208,23 @@ function doDisableBrush(controller) {
 
     release(zr, MUTEX_RESOURCE_KEY, controller._uid);
 
-    each$12(controller._handlers, function (handler, eventName) {
-        zr.off(eventName, handler);
-    });
+    unmountHandlers(zr, controller._handlers);
 
     controller._brushType = controller._brushOption = null;
 }
 
+function mountHandlers(zr, handlers) {
+    each$12(handlers, function (handler, eventName) {
+        zr.on(eventName, handler);
+    });
+}
+
+function unmountHandlers(zr, handlers) {
+    each$12(handlers, function (handler, eventName) {
+        zr.off(eventName, handler);
+    });
+}
+
 function createCover(controller, brushOption) {
     var cover = coverRenderers[brushOption.brushType].createCover(controller, brushOption);
     cover.__brushOption = brushOption;
@@ -60508,8 +61538,14 @@ function pointsToRect(points) {
 }
 
 function resetCursor(controller, e, localCursorPoint) {
-    // Check active
-    if (!controller._brushType) {
+    if (
+        // Check active
+        !controller._brushType
+        // resetCursor should be always called when mouse is in zr area,
+        // but not called when mouse is out of zr area to avoid bad influence
+        // if `mousemove`, `mouseup` are triggered from `document` event.
+        || isOutsideZrArea(controller, e)
+    ) {
         return;
     }
 
@@ -60613,7 +61649,7 @@ function determineBrushType(brushType, panel) {
     return brushType;
 }
 
-var mouseHandlers = {
+var pointerHandlers = {
 
     mousedown: function (e) {
         if (this._dragging) {
@@ -60638,56 +61674,34 @@ var mouseHandlers = {
     },
 
     mousemove: function (e) {
-        var lastPoint = this._lastMouseMovePoint;
-        lastPoint.x = e.offsetX;
-        lastPoint.y = e.offsetY;
+        var x = e.offsetX;
+        var y = e.offsetY;
 
-        var localCursorPoint = this.group.transformCoordToLocal(lastPoint.x, lastPoint.y);
+        var localCursorPoint = this.group.transformCoordToLocal(x, y);
 
         resetCursor(this, e, localCursorPoint);
 
         if (this._dragging) {
-
             preventDefault(e);
-
             var eventParams = updateCoverByMouse(this, e, localCursorPoint, false);
-
             eventParams && trigger$1(this, eventParams);
         }
     },
 
     mouseup: function (e) {
         handleDragEnd(this, e);
-    },
-
-    globalout: function (e) {
-        handleDragEnd(this, e, true);
     }
 };
 
-function handleDragEnd(controller, e, isGlobalOut) {
-    if (controller._dragging) {
 
-        // Just be worried about bring some side effect to the world
-        // out of echarts, we do not `preventDefault` for globalout.
-        !isGlobalOut && preventDefault(e);
+function handleDragEnd(controller, e) {
+    if (controller._dragging) {
+        preventDefault(e);
 
-        var pointerX = e.offsetX;
-        var pointerY = e.offsetY;
-        var lastPoint = controller._lastMouseMovePoint;
-        if (isGlobalOut) {
-            pointerX = lastPoint.x;
-            pointerY = lastPoint.y;
-        }
+        var x = e.offsetX;
+        var y = e.offsetY;
 
-        var localCursorPoint = controller.group.transformCoordToLocal(pointerX, pointerY);
-        // FIXME
-        // Here `e` is used only in `onIrrelevantElement` finally. And it's OK
-        // that pass the `e` of `globalout` to `onIrrelevantElement`. But it is
-        // not a good design of these interfaces. However, we do not refactor
-        // these code now because the implementation of `onIrrelevantElement`
-        // need to be discussed and probably be changed in future, becuase it
-        // slows down the performance of zrender in some cases.
+        var localCursorPoint = controller.group.transformCoordToLocal(x, y);
         var eventParams = updateCoverByMouse(controller, e, localCursorPoint, true);
 
         controller._dragging = false;
@@ -60699,6 +61713,12 @@ function handleDragEnd(controller, e, isGlobalOut) {
     }
 }
 
+function isOutsideZrArea(controller, x, y) {
+    var zr = controller._zr;
+    return x < 0 || x > zr.getWidth() || y < 0 || y > zr.getHeight();
+}
+
+
 /**
  * key: brushType
  * @type {Object}
@@ -62162,6 +63182,7 @@ extendChartView({
             if (itemModel.get('focusNodeAdjacency')) {
                 el.off('mouseover').on('mouseover', function () {
                     if (!sankeyView._focusAdjacencyDisabled) {
+                        sankeyView._clearTimer();
                         api.dispatchAction({
                             type: 'focusNodeAdjacency',
                             seriesId: seriesModel.id,
@@ -62171,10 +63192,7 @@ extendChartView({
                 });
                 el.off('mouseout').on('mouseout', function () {
                     if (!sankeyView._focusAdjacencyDisabled) {
-                        api.dispatchAction({
-                            type: 'unfocusNodeAdjacency',
-                            seriesId: seriesModel.id
-                        });
+                        sankeyView._dispatchUnfocus(api);
                     }
                 });
             }
@@ -62185,6 +63203,7 @@ extendChartView({
             if (edgeModel.get('focusNodeAdjacency')) {
                 el.off('mouseover').on('mouseover', function () {
                     if (!sankeyView._focusAdjacencyDisabled) {
+                        sankeyView._clearTimer();
                         api.dispatchAction({
                             type: 'focusNodeAdjacency',
                             seriesId: seriesModel.id,
@@ -62194,10 +63213,7 @@ extendChartView({
                 });
                 el.off('mouseout').on('mouseout', function () {
                     if (!sankeyView._focusAdjacencyDisabled) {
-                        api.dispatchAction({
-                            type: 'unfocusNodeAdjacency',
-                            seriesId: seriesModel.id
-                        });
+                        sankeyView._dispatchUnfocus(api);
                     }
                 });
             }
@@ -62212,7 +63228,28 @@ extendChartView({
         this._data = seriesModel.getData();
     },
 
-    dispose: function () {},
+    dispose: function () {
+        this._clearTimer();
+    },
+
+    _dispatchUnfocus: function (api) {
+        var self = this;
+        this._clearTimer();
+        this._unfocusDelayTimer = setTimeout(function () {
+            self._unfocusDelayTimer = null;
+            api.dispatchAction({
+                type: 'unfocusNodeAdjacency',
+                seriesId: self._model.id
+            });
+        }, 500);
+    },
+
+    _clearTimer: function () {
+        if (this._unfocusDelayTimer) {
+            clearTimeout(this._unfocusDelayTimer);
+            this._unfocusDelayTimer = null;
+        }
+    },
 
     focusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
         var data = this._model.getData();
@@ -62361,7 +63398,7 @@ var sankeyLayout = function (ecModel, api, payload) {
         var nodeWidth = seriesModel.get('nodeWidth');
         var nodeGap = seriesModel.get('nodeGap');
 
-        var layoutInfo = getViewRect$3(seriesModel, api);
+        var layoutInfo = getViewRect$4(seriesModel, api);
 
         seriesModel.layoutInfo = layoutInfo;
 
@@ -62396,7 +63433,7 @@ var sankeyLayout = function (ecModel, api, payload) {
  * @param {module:echarts/ExtensionAPI} api  provide the API list that the developer can call
  * @return {module:zrender/core/BoundingRect}  size of rect to draw the sankey view
  */
-function getViewRect$3(seriesModel, api) {
+function getViewRect$4(seriesModel, api) {
     return getLayoutRect(
         seriesModel.getBoxLayoutParams(), {
             width: api.getWidth(),
@@ -62953,7 +63990,7 @@ var seriesModelMixin = {
         var addOrdinal;
 
         // FIXME
-        // 考虑时间轴
+        // Consider time axis.
 
         if (xAxisType === 'category') {
             option.layout = 'horizontal';
@@ -63001,25 +64038,29 @@ var seriesModelMixin = {
         }
 
         var defaultValueDimensions = this.defaultValueDimensions;
+        var coordDimensions = [{
+            name: baseAxisDim,
+            type: getDimensionTypeByAxis(baseAxisType),
+            ordinalMeta: ordinalMeta,
+            otherDims: {
+                tooltip: false,
+                itemName: 0
+            },
+            dimsDef: ['base']
+        }, {
+            name: otherAxisDim,
+            type: getDimensionTypeByAxis(otherAxisType),
+            dimsDef: defaultValueDimensions.slice()
+        }];
 
         return createListSimply(
             this,
             {
-                coordDimensions: [{
-                    name: baseAxisDim,
-                    type: getDimensionTypeByAxis(baseAxisType),
-                    ordinalMeta: ordinalMeta,
-                    otherDims: {
-                        tooltip: false,
-                        itemName: 0
-                    },
-                    dimsDef: ['base']
-                }, {
-                    name: otherAxisDim,
-                    type: getDimensionTypeByAxis(otherAxisType),
-                    dimsDef: defaultValueDimensions.slice()
-                }],
-                dimensionsCount: defaultValueDimensions.length + 1
+                coordDimensions: coordDimensions,
+                dimensionsCount: defaultValueDimensions.length + 1,
+                encodeDefaulter: curry(
+                    makeSeriesEncodeForAxisCoordSys, coordDimensions, this
+                )
             }
         );
     },
@@ -64394,7 +65435,7 @@ SeriesModel.extend({
     dependencies: ['grid', 'polar'],
 
     getInitialData: function (option, ecModel) {
-        return createListFromArray(this.getSource(), this);
+        return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true});
     },
 
     brushSelector: 'point',
@@ -66521,10 +67562,10 @@ extendChartView({
 
                 rect = new Rect({
                     shape: {
-                        x: point[0] - width / 2,
-                        y: point[1] - height / 2,
-                        width: width,
-                        height: height
+                        x: Math.floor(point[0] - width / 2),
+                        y: Math.floor(point[1] - height / 2),
+                        width: Math.ceil(width),
+                        height: Math.ceil(height)
                     },
                     style: {
                         fill: data.getItemVisual(idx, 'color'),
@@ -70238,9 +71279,9 @@ var ThemeRiverSeries = SeriesModel.extend({
         // Put this function here is for the sake of consistency of code style.
         // Enable legend selection for each data item
         // Use a function instead of direct access because data reference may changed
-        this.legendDataProvider = function () {
-            return this.getRawData();
-        };
+        this.legendVisualProvider = new LegendVisualProvider(
+            bind(this.getData, this), bind(this.getRawData, this)
+        );
     },
 
     /**
@@ -74232,7 +75273,7 @@ CoordinateSystemManager.register('polar', polarCreator);
 * under the License.
 */
 
-var elementList$1 = ['axisLine', 'axisLabel', 'axisTick', 'splitLine', 'splitArea'];
+var elementList$1 = ['axisLine', 'axisLabel', 'axisTick', 'minorTick', 'splitLine', 'minorSplitLine', 'splitArea'];
 
 function getAxisLineShape(polar, rExtent, angle) {
     rExtent[1] > rExtent[0] && (rExtent = rExtent.slice().reverse());
@@ -74281,6 +75322,8 @@ AxisView.extend({
         var radiusExtent = polar.getRadiusAxis().getExtent();
 
         var ticksAngles = angleAxis.getTicksCoords();
+        var minorTickAngles = angleAxis.getMinorTicksCoords();
+
         var labels = map(angleAxis.getViewLabels(), function (labelItem) {
             var labelItem = clone(labelItem);
             labelItem.coord = angleAxis.dataToCoord(labelItem.tickValue);
@@ -74294,7 +75337,7 @@ AxisView.extend({
             if (angleAxisModel.get(name + '.show')
                 && (!angleAxis.scale.isBlank() || name === 'axisLine')
             ) {
-                this['_' + name](angleAxisModel, polar, ticksAngles, radiusExtent, labels);
+                this['_' + name](angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent, labels);
             }
         }, this);
     },
@@ -74302,7 +75345,7 @@ AxisView.extend({
     /**
      * @private
      */
-    _axisLine: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
+    _axisLine: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
         var lineStyleModel = angleAxisModel.getModel('axisLine.lineStyle');
 
         // extent id of the axis radius (r0 and r)
@@ -74342,7 +75385,7 @@ AxisView.extend({
     /**
      * @private
      */
-    _axisTick: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
+    _axisTick: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
         var tickModel = angleAxisModel.getModel('axisTick');
 
         var tickLen = (tickModel.get('inside') ? -1 : 1) * tickModel.get('length');
@@ -74368,7 +75411,45 @@ AxisView.extend({
     /**
      * @private
      */
-    _axisLabel: function (angleAxisModel, polar, ticksAngles, radiusExtent, labels) {
+    _minorTick: function (angleAxisModel, polar, tickAngles, minorTickAngles, radiusExtent) {
+        if (!minorTickAngles.length) {
+            return;
+        }
+
+        var tickModel = angleAxisModel.getModel('axisTick');
+        var minorTickModel = angleAxisModel.getModel('minorTick');
+
+        var tickLen = (tickModel.get('inside') ? -1 : 1) * minorTickModel.get('length');
+        var radius = radiusExtent[getRadiusIdx(polar)];
+
+        var lines = [];
+
+        for (var i = 0; i < minorTickAngles.length; i++) {
+            for (var k = 0; k < minorTickAngles[i].length; k++) {
+                lines.push(new Line({
+                    shape: getAxisLineShape(polar, [radius, radius + tickLen], minorTickAngles[i][k].coord)
+                }));
+            }
+        }
+
+        this.group.add(mergePath(
+            lines, {
+                style: defaults(
+                    minorTickModel.getModel('lineStyle').getLineStyle(),
+                    defaults(
+                        tickModel.getLineStyle(), {
+                            stroke: angleAxisModel.get('axisLine.lineStyle.color')
+                        }
+                    )
+                )
+            }
+        ));
+    },
+
+    /**
+     * @private
+     */
+    _axisLabel: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent, labels) {
         var rawCategoryData = angleAxisModel.getCategories(true);
 
         var commonLabelModel = angleAxisModel.getModel('axisLabel');
@@ -74423,7 +75504,7 @@ AxisView.extend({
     /**
      * @private
      */
-    _splitLine: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
+    _splitLine: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
         var splitLineModel = angleAxisModel.getModel('splitLine');
         var lineStyleModel = splitLineModel.getModel('lineStyle');
         var lineColors = lineStyleModel.get('color');
@@ -74457,7 +75538,35 @@ AxisView.extend({
     /**
      * @private
      */
-    _splitArea: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
+    _minorSplitLine: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
+        if (!minorTickAngles.length) {
+            return;
+        }
+
+        var minorSplitLineModel = angleAxisModel.getModel('minorSplitLine');
+        var lineStyleModel = minorSplitLineModel.getModel('lineStyle');
+
+        var lines = [];
+
+        for (var i = 0; i < minorTickAngles.length; i++) {
+            for (var k = 0; k < minorTickAngles[i].length; k++) {
+                lines.push(new Line({
+                    shape: getAxisLineShape(polar, radiusExtent, minorTickAngles[i][k].coord)
+                }));
+            }
+        }
+
+        this.group.add(mergePath(lines, {
+            style: lineStyleModel.getLineStyle(),
+            silent: true,
+            z: angleAxisModel.get('z')
+        }));
+    },
+
+    /**
+     * @private
+     */
+    _splitArea: function (angleAxisModel, polar, ticksAngles, minorTickAngles, radiusExtent) {
         if (!ticksAngles.length) {
             return;
         }
@@ -74551,7 +75660,7 @@ var axisBuilderAttrs$3 = [
     'axisLine', 'axisTickLabel', 'axisName'
 ];
 var selfBuilderAttrs$1 = [
-    'splitLine', 'splitArea'
+    'splitLine', 'splitArea', 'minorSplitLine'
 ];
 
 AxisView.extend({
@@ -74569,6 +75678,7 @@ AxisView.extend({
         var polar = radiusAxis.polar;
         var angleAxis = polar.getAngleAxis();
         var ticksCoords = radiusAxis.getTicksCoords();
+        var minorTicksCoords = radiusAxis.getMinorTicksCoords();
         var axisAngle = angleAxis.getExtent()[0];
         var radiusExtent = radiusAxis.getExtent();
 
@@ -74579,7 +75689,7 @@ AxisView.extend({
 
         each$1(selfBuilderAttrs$1, function (name) {
             if (radiusAxisModel.get(name + '.show') && !radiusAxis.scale.isBlank()) {
-                this['_' + name](radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords);
+                this['_' + name](radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords, minorTicksCoords);
             }
         }, this);
     },
@@ -74605,8 +75715,7 @@ AxisView.extend({
                     cx: polar.cx,
                     cy: polar.cy,
                     r: ticksCoords[i].coord
-                },
-                silent: true
+                }
             }));
         }
 
@@ -74626,6 +75735,39 @@ AxisView.extend({
     /**
      * @private
      */
+    _minorSplitLine: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords, minorTicksCoords) {
+        if (!minorTicksCoords.length) {
+            return;
+        }
+
+        var minorSplitLineModel = radiusAxisModel.getModel('minorSplitLine');
+        var lineStyleModel = minorSplitLineModel.getModel('lineStyle');
+
+        var lines = [];
+
+        for (var i = 0; i < minorTicksCoords.length; i++) {
+            for (var k = 0; k < minorTicksCoords[i].length; k++) {
+                lines.push(new Circle({
+                    shape: {
+                        cx: polar.cx,
+                        cy: polar.cy,
+                        r: minorTicksCoords[i][k].coord
+                    }
+                }));
+            }
+        }
+
+        this.group.add(mergePath(lines, {
+            style: defaults({
+                fill: null
+            }, lineStyleModel.getLineStyle()),
+            silent: true
+        }));
+    },
+
+    /**
+     * @private
+     */
     _splitArea: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords) {
         if (!ticksCoords.length) {
             return;
@@ -77139,6 +78281,11 @@ extendComponentView({
             var featureModel = new Model(featureOpt, toolboxModel, toolboxModel.ecModel);
             var feature;
 
+            // FIX#11236, merge feature title from MagicType newOption. TODO: consider seriesIndex ?
+            if (payload && payload.newTitle != null) {
+                featureOpt.title = payload.newTitle;
+            }
+
             if (featureName && !oldName) { // Create
                 if (isUserFeatureName(featureName)) {
                     feature = {
@@ -77478,6 +78625,7 @@ register$1(
 */
 
 var magicTypeLang = lang.toolbox.magicType;
+var INNER_STACK_KEYWORD = '__ec_magicType_stack__';
 
 function MagicType(model) {
     this.model = model;
@@ -77491,8 +78639,7 @@ MagicType.defaultOption = {
         /* eslint-disable */
         line: 'M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4',
         bar: 'M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7',
-        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z', // jshint ignore:line
-        tiled: 'M2.3,2.2h22.8V25H2.3V2.2z M35,2.2h22.8V25H35V2.2zM2.3,35h22.8v22.8H2.3V35z M35,35h22.8v22.8H35V35z'
+        stack: 'M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z' // jshint ignore:line
         /* eslint-enable */
     },
     // `line`, `bar`, `stack`, `tiled`
@@ -77543,26 +78690,20 @@ var seriesOptGenreator = {
         }
     },
     'stack': function (seriesType, seriesId, seriesModel, model) {
+        var isStack = seriesModel.get('stack') === INNER_STACK_KEYWORD;
         if (seriesType === 'line' || seriesType === 'bar') {
+            model.setIconStatus('stack', isStack ? 'normal' : 'emphasis');
             return merge({
                 id: seriesId,
-                stack: '__ec_magicType_stack__'
+                stack: isStack ? '' : INNER_STACK_KEYWORD
             }, model.get('option.stack') || {}, true);
         }
-    },
-    'tiled': function (seriesType, seriesId, seriesModel, model) {
-        if (seriesType === 'line' || seriesType === 'bar') {
-            return merge({
-                id: seriesId,
-                stack: ''
-            }, model.get('option.tiled') || {}, true);
-        }
     }
 };
 
 var radioTypes = [
     ['line', 'bar'],
-    ['stack', 'tiled']
+    ['stack']
 ];
 
 proto$3.onclick = function (ecModel, api, type) {
@@ -77627,10 +78768,21 @@ proto$3.onclick = function (ecModel, api, type) {
             }
         }, generateNewSeriesTypes
     );
+
+    var newTitle;
+    // Change title of stack
+    if (type === 'stack') {
+        var isStack = newOption.series && newOption.series[0] && newOption.series[0].stack === INNER_STACK_KEYWORD;
+        newTitle = isStack
+            ? merge({ stack: magicTypeLang.title.tiled }, magicTypeLang.title)
+            : clone(magicTypeLang.title);
+    }
+
     api.dispatchAction({
         type: 'changeMagicType',
         currentType: type,
-        newOption: newOption
+        newOption: newOption,
+        newTitle: newTitle
     });
 };
 
@@ -84364,7 +85516,7 @@ TimelineView.extend({
     _layout: function (timelineModel, api) {
         var labelPosOpt = timelineModel.get('label.position');
         var orient = timelineModel.get('orient');
-        var viewRect = getViewRect$4(timelineModel, api);
+        var viewRect = getViewRect$5(timelineModel, api);
         // Auto label offset.
         if (labelPosOpt == null || labelPosOpt === 'auto') {
             labelPosOpt = orient === 'horizontal'
@@ -84836,7 +85988,7 @@ TimelineView.extend({
 
 });
 
-function getViewRect$4(model, api) {
+function getViewRect$5(model, api) {
     return getLayoutRect(
         model.getBoxLayoutParams(),
         {
@@ -86646,9 +87798,9 @@ var LegendModel = extendComponentModel({
             availableNames.push(seriesName);
             var isPotential;
 
-            if (seriesModel.legendDataProvider) {
-                var data = seriesModel.legendDataProvider();
-                var names = data.mapArray(data.getName);
+            if (seriesModel.legendVisualProvider) {
+                var provider = seriesModel.legendVisualProvider;
+                var names = provider.getAllNames();
 
                 if (!ecModel.isSeriesFiltered(seriesModel)) {
                     availableNames = availableNames.concat(names);
@@ -87158,7 +88310,7 @@ var LegendView = extendComponentView({
                 return;
             }
 
-            // Series legend
+            // Legend to control series.
             if (seriesModel) {
                 var data = seriesModel.getData();
                 var color = data.getVisual('color');
@@ -87187,29 +88339,31 @@ var LegendView = extendComponentView({
                     selectMode
                 );
 
-                itemGroup.on('click', curry$6(dispatchSelectAction, name, api))
+                itemGroup.on('click', curry$6(dispatchSelectAction, name, null, api, excludeSeriesId))
                     .on('mouseover', curry$6(dispatchHighlightAction, seriesModel.name, null, api, excludeSeriesId))
                     .on('mouseout', curry$6(dispatchDownplayAction, seriesModel.name, null, api, excludeSeriesId));
 
                 legendDrawnMap.set(name, true);
             }
             else {
-                // Data legend of pie, funnel
+                // Legend to control data. In pie and funnel.
                 ecModel.eachRawSeries(function (seriesModel) {
+
                     // In case multiple series has same data name
                     if (legendDrawnMap.get(name)) {
                         return;
                     }
 
-                    if (seriesModel.legendDataProvider) {
-                        var data = seriesModel.legendDataProvider();
-                        var idx = data.indexOfName(name);
-                        if (idx < 0) {
+                    if (seriesModel.legendVisualProvider) {
+                        var provider = seriesModel.legendVisualProvider;
+                        if (!provider.containName(name)) {
                             return;
                         }
 
-                        var color = data.getItemVisual(idx, 'color');
-                        var borderColor = data.getItemVisual(idx, 'borderColor');
+                        var idx = provider.indexOfName(name);
+
+                        var color = provider.getItemVisual(idx, 'color');
+                        var borderColor = provider.getItemVisual(idx, 'borderColor');
 
                         var legendSymbolType = 'roundRect';
 
@@ -87221,7 +88375,7 @@ var LegendView = extendComponentView({
                         );
 
                         // FIXME: consider different series has items with the same name.
-                        itemGroup.on('click', curry$6(dispatchSelectAction, name, api))
+                        itemGroup.on('click', curry$6(dispatchSelectAction, null, name, api, excludeSeriesId))
                             // Should not specify the series name, consider legend controls
                             // more than one pie series.
                             .on('mouseover', curry$6(dispatchHighlightAction, null, name, api, excludeSeriesId))
@@ -87501,11 +88655,15 @@ function setSymbolStyle(symbol, symbolType, legendModelItemStyle, borderColor, i
     return symbol.setStyle(itemStyle);
 }
 
-function dispatchSelectAction(name, api) {
+function dispatchSelectAction(seriesName, dataName, api, excludeSeriesId) {
+    // downplay before unselect
+    dispatchDownplayAction(seriesName, dataName, api, excludeSeriesId);
     api.dispatchAction({
         type: 'legendToggleSelect',
-        name: name
+        name: seriesName != null ? seriesName : dataName
     });
+    // highlight after select
+    dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId);
 }
 
 function dispatchHighlightAction(seriesName, dataName, api, excludeSeriesId) {
@@ -88757,10 +89915,6 @@ var SliderZoomView = DataZoomView.extend({
             draggable: true,
             cursor: getCursor(this._orient),
             drift: bind$5(this._onDragMove, this, 'all'),
-            onmousemove: function (e) {
-                // Fot mobile devicem, prevent screen slider on the button.
-                stop(e.event);
-            },
             ondragstart: bind$5(this._showDataInfo, this, true),
             ondragend: bind$5(this._onDragEnd, this),
             onmouseover: bind$5(this._showDataInfo, this, true),
@@ -88796,10 +89950,6 @@ var SliderZoomView = DataZoomView.extend({
                     cursor: getCursor(this._orient),
                     draggable: true,
                     drift: bind$5(this._onDragMove, this, handleIndex),
-                    onmousemove: function (e) {
-                        // Fot mobile devicem, prevent screen slider on the button.
-                        stop(e.event);
-                    },
                     ondragend: bind$5(this._onDragEnd, this),
                     onmouseover: bind$5(this._showDataInfo, this, true),
                     onmouseout: bind$5(this._showDataInfo, this, false)
@@ -89021,9 +90171,12 @@ var SliderZoomView = DataZoomView.extend({
         handleLabels[1].attr('invisible', !showOrHide);
     },
 
-    _onDragMove: function (handleIndex, dx, dy) {
+    _onDragMove: function (handleIndex, dx, dy, event) {
         this._dragging = true;
 
+        // For mobile device, prevent screen slider on the button.
+        stop(event.event);
+
         // Transform dx, dy to bar coordination.
         var barTransform = this._displayables.barGroup.getLocalTransform();
         var vertex = applyTransform$1([dx, dy], barTransform, true);
@@ -94294,6 +95447,9 @@ svgPath.brush = function (el) {
     if (style.text != null) {
         svgTextDrawRectText(el, el.getBoundingRect());
     }
+    else {
+        removeOldTextNode(el);
+    }
 };
 
 /***************************************************
@@ -94341,6 +95497,9 @@ svgImage.brush = function (el) {
     if (style.text != null) {
         svgTextDrawRectText(el, el.getBoundingRect());
     }
+    else {
+        removeOldTextNode(el);
+    }
 };
 
 /***************************************************
@@ -94541,6 +95700,18 @@ function updateTextLocation(tspan, textAlign, x, y) {
     attr(tspan, 'y', y);
 }
 
+function removeOldTextNode(el) {
+    if (el && el.__textSvgEl) {
+        // textSvgEl may has no parentNode if el has been removed temporary.
+        if (el.__textSvgEl.parentNode) {
+            el.__textSvgEl.parentNode.removeChild(el.__textSvgEl);
+        }
+        el.__textSvgEl = null;
+        el.__tspanList = [];
+        el.__text = null;
+    }
+}
+
 svgText.drawRectText = svgTextDrawRectText;
 
 svgText.brush = function (el) {
@@ -94548,6 +95719,9 @@ svgText.brush = function (el) {
     if (style.text != null) {
         svgTextDrawRectText(el, false);
     }
+    else {
+        removeOldTextNode(el);
+    }
 };
 
 // Myers' Diff Algorithm
@@ -95802,8 +96976,8 @@ SVGPainter.prototype = {
             else if (!item.removed) {
                 for (var k = 0; k < item.count; k++) {
                     var displayable = newVisibleList[item.indices[k]];
-                    prevSvgElement = getTextSvgElement(displayable)
-                        || getSvgElement(displayable) || prevSvgElement;
+                    var svgElement = getSvgElement(displayable);
+                    var textSvgElement = getTextSvgElement(displayable);
 
                     var svgElement = getSvgElement(displayable);
                     var textSvgElement = getTextSvgElement(displayable);
@@ -95817,6 +96991,12 @@ SVGPainter.prototype = {
                         .addWithoutUpdate(svgElement || textSvgElement, displayable);
 
                     this.clipPathManager.markUsed(displayable);
+
+                    if (textSvgElement) { // Insert text.
+                        insertAfter(svgRoot, textSvgElement, svgElement);
+                    }
+                    prevSvgElement = svgElement
+                        || textSvgElement || prevSvgElement;
                 }
             }
         }
diff --git a/dist/echarts-en.js.map b/dist/echarts-en.js.map
index 7139c21..a994ff2 100644
--- a/dist/echarts-en.js.map
+++ b/dist/echarts-en.js.map
@@ -1 +1 @@
-{"version":3,"file":"echarts-en.js","sources":["../src/config.js","../../zrender/src/core/guid.js","../../zrender/src/core/env.js","../../zrender/src/core/util.js","../../zrender/src/core/vector.js","../../zrender/src/mixin/Draggable.js","../../zrender/src/mixin/Eventful.js","../../zrender/src/core/fourPointsTransform.js","../../zrender/src/core/event.js","../../zrender/src/core/GestureMgr.js","../../zrender/src/Handler.js","../../zrender/src/core/matrix.js","../../zrender/src/mixin/Tran [...]
\ No newline at end of file
+{"version":3,"file":"echarts-en.js","sources":["../src/config.js","../../zrender/src/core/guid.js","../../zrender/src/core/env.js","../../zrender/src/core/util.js","../../zrender/src/core/vector.js","../../zrender/src/mixin/Draggable.js","../../zrender/src/mixin/Eventful.js","../../zrender/src/core/fourPointsTransform.js","../../zrender/src/core/event.js","../../zrender/src/core/GestureMgr.js","../../zrender/src/Handler.js","../../zrender/src/core/matrix.js","../../zrender/src/mixin/Tran [...]
\ No newline at end of file
diff --git a/dist/echarts-en.min.js b/dist/echarts-en.min.js
index d09e19f..456243b 100644
--- a/dist/echarts-en.min.js
+++ b/dist/echarts-en.min.js
@@ -19,4 +19,4 @@
 */
 
 
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.echarts={})}(this,function(t){"use strict";var e=2311,n=function(){return e++},v="object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?{browser:{},os:{},node:!1,wxa:!0,canvasSupported:!0,svgSupported:!1,touchEventsSupported:!0,domSupported:!1}:"undefined"==typeof document&&"undefined"!=typeof self?{browser:{},os:{},node:!1,worker:!0,canvasS [...]
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.echarts={})}(this,function(t){"use strict";var e=2311,n=function(){return e++},v="object"==typeof wx&&"function"==typeof wx.getSystemInfoSync?{browser:{},os:{},node:!1,wxa:!0,canvasSupported:!0,svgSupported:!1,touchEventsSupported:!0,domSupported:!1}:"undefined"==typeof document&&"undefined"!=typeof self?{browser:{},os:{},node:!1,worker:!0,canvasS [...]
diff --git a/dist/echarts-en.simple.js b/dist/echarts-en.simple.js
index d948791..cd00713 100644
--- a/dist/echarts-en.simple.js
+++ b/dist/echarts-en.simple.js
@@ -208,12 +208,17 @@ function detect(ua) {
         // default, so we dont check navigator.maxTouchPoints for them here.
         touchEventsSupported: 'ontouchstart' in window && !browser.ie && !browser.edge,
         // <http://caniuse.com/#search=pointer%20event>.
-        pointerEventsSupported: 'onpointerdown' in window
-            // Firefox supports pointer but not by default, only MS browsers are reliable on pointer
+        pointerEventsSupported:
+            // (1) Firefox supports pointer but not by default, only MS browsers are reliable on pointer
             // events currently. So we dont use that on other browsers unless tested sufficiently.
-            // Although IE 10 supports pointer event, it use old style and is different from the
+            // For example, in iOS 13 Mobile Chromium 78, if the touching behavior starts page
+            // scroll, the `pointermove` event can not be fired any more. That will break some
+            // features like "pan horizontally to move something and pan vertically to page scroll".
+            // The horizontal pan probably be interrupted by the casually triggered page scroll.
+            // (2) Although IE 10 supports pointer event, it use old style and is different from the
             // standard. So we exclude that. (IE 10 is hardly used on touch device)
-            && (browser.edge || (browser.ie && browser.version >= 11)),
+            'onpointerdown' in window
+                && (browser.edge || (browser.ie && browser.version >= 11)),
         // passiveSupported: detectPassiveSupport()
         domSupported: typeof document !== 'undefined'
     };
@@ -1149,7 +1154,11 @@ function Draggable() {
     this.on('mousedown', this._dragStart, this);
     this.on('mousemove', this._drag, this);
     this.on('mouseup', this._dragEnd, this);
-    this.on('globalout', this._dragEnd, this);
+    // `mosuemove` and `mouseup` can be continue to fire when dragging.
+    // See [Drag outside] in `Handler.js`. So we do not need to trigger
+    // `_dragEnd` when globalout. That would brings better user experience.
+    // this.on('globalout', this._dragEnd, this);
+
     // this._dropTarget = null;
     // this._draggingTarget = null;
 
@@ -1250,7 +1259,7 @@ var arrySlice = Array.prototype.slice;
  *        param: {string} eventType
  *        param: {string|Object} query
  *        return: {boolean}
- * @param {Function} [eventProcessor.afterTrigger] Call after all handlers called.
+ * @param {Function} [eventProcessor.afterTrigger] Called after all handlers called.
  *        param: {string} eventType
  */
 var Eventful = function (eventProcessor) {
@@ -1300,8 +1309,10 @@ Eventful.prototype = {
     /**
      * Unbind a event.
      *
-     * @param {string} event The event name.
+     * @param {string} [event] The event name.
+     *        If no `event` input, "off" all listeners.
      * @param {Function} [handler] The event handler.
+     *        If no `handler` input, "off" all listeners of the `event`.
      */
     off: function (event, handler) {
         var _h = this._$handlers;
@@ -1459,6 +1470,7 @@ Eventful.prototype = {
     }
 };
 
+
 function normalizeQuery(host, query) {
     var eventProcessor = host._$eventProcessor;
     if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
@@ -1777,6 +1789,18 @@ function preparePointerTransformer(markers, saved) {
 }
 
 /**
+ * Find native event compat for legency IE.
+ * Should be called at the begining of a native event listener.
+ *
+ * @param {Event} [e] Mouse event or touch event or pointer event.
+ *        For lagency IE, we use `window.event` is used.
+ * @return {Event} The native event.
+ */
+function getNativeEvent(e) {
+    return e || window.event;
+}
+
+/**
  * Normalize the coordinates of the input event.
  *
  * Get the `e.zrX` and `e.zrY`, which are relative to the top-left of
@@ -1790,14 +1814,14 @@ function preparePointerTransformer(markers, saved) {
  * between the result coords and the parameters `el` and `calculate`.
  *
  * @param {HTMLElement} el DOM element.
- * @param {Event} [e] Mouse event or touch event. For lagency IE,
- *        do not need to input it and `window.event` is used.
+ * @param {Event} [e] See `getNativeEvent`.
  * @param {boolean} [calculate=false] Whether to force calculate
  *        the coordinates but not use ones provided by browser.
+ * @return {UIEvent} The normalized native UIEvent.
  */
 function normalizeEvent(el, e, calculate) {
 
-    e = e || window.event;
+    e = getNativeEvent(e);
 
     if (e.zrX != null) {
         return e;
@@ -1837,8 +1861,11 @@ function normalizeEvent(el, e, calculate) {
  * @param {HTMLElement} el
  * @param {string} name
  * @param {Function} handler
+ * @param {Object|boolean} opt If boolean, means `opt.capture`
+ * @param {boolean} [opt.capture=false]
+ * @param {boolean} [opt.passive=false]
  */
-function addEventListener(el, name, handler) {
+function addEventListener(el, name, handler, opt) {
     if (isDomLevel2) {
         // Reproduct the console warning:
         // [Violation] Added non-passive event listener to a scroll-blocking <some> event.
@@ -1861,16 +1888,24 @@ function addEventListener(el, name, handler) {
         //     // By default, the third param of el.addEventListener is `capture: false`.
         //     : void 0;
         // el.addEventListener(name, handler /* , opts */);
-        el.addEventListener(name, handler);
+        el.addEventListener(name, handler, opt);
     }
     else {
+        // For simplicity, do not implement `setCapture` for IE9-.
         el.attachEvent('on' + name, handler);
     }
 }
 
-function removeEventListener(el, name, handler) {
+/**
+ * Parameter are the same as `addEventListener`.
+ *
+ * Notice that if a listener is registered twice, one with capture and one without,
+ * remove each one separately. Removal of a capturing listener does not affect a
+ * non-capturing version of the same listener, and vice versa.
+ */
+function removeEventListener(el, name, handler, opt) {
     if (isDomLevel2) {
-        el.removeEventListener(name, handler);
+        el.removeEventListener(name, handler, opt);
     }
     else {
         el.detachEvent('on' + name, handler);
@@ -2023,6 +2058,65 @@ var recognizers = {
     // Only pinch currently.
 };
 
+/**
+ * [The interface between `Handler` and `HandlerProxy`]:
+ *
+ * The default `HandlerProxy` only support the common standard web environment
+ * (e.g., standalone browser, headless browser, embed browser in mobild APP, ...).
+ * But `HandlerProxy` can be replaced to support more non-standard environment
+ * (e.g., mini app), or to support more feature that the default `HandlerProxy`
+ * not provided (like echarts-gl did).
+ * So the interface between `Handler` and `HandlerProxy` should be stable. Do not
+ * make break changes util inevitable. The interface include the public methods
+ * of `Handler` and the events listed in `handlerNames` below, by which `HandlerProxy`
+ * drives `Handler`.
+ */
+
+/**
+ * [Drag outside]:
+ *
+ * That is, triggering `mousemove` and `mouseup` event when the pointer is out of the
+ * zrender area when dragging. That is important for the improvement of the user experience
+ * when dragging something near the boundary without being terminated unexpectedly.
+ *
+ * We originally consider to introduce new events like `pagemovemove` and `pagemouseup`
+ * to resolve this issue. But some drawbacks of it is described in
+ * https://github.com/ecomfe/zrender/pull/536#issuecomment-560286899
+ *
+ * Instead, we referenced the specifications:
+ * https://www.w3.org/TR/touch-events/#the-touchmove-event
+ * https://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#event-type-mousemove
+ * where the the mousemove/touchmove can be continue to fire if the user began a drag
+ * operation and the pointer has left the boundary. (for the mouse event, browsers
+ * only do it on `document` and when the pointer has left the boundary of the browser.)
+ *
+ * So the default `HandlerProxy` supports this feature similarly: if it is in the dragging
+ * state (see `pointerCapture` in `HandlerProxy`), the `mousemove` and `mouseup` continue
+ * to fire until release the pointer. That is implemented by listen to those event on
+ * `document`.
+ * If we implement some other `HandlerProxy` only for touch device, that would be easier.
+ * The touch event support this feature by default.
+ *
+ * Note:
+ * There might be some cases that the mouse event can not be
+ * received on `document`. For example,
+ * (A) `useCapture` is not supported and some user defined event listeners on the ancestor
+ * of zr dom throw Error .
+ * (B) `useCapture` is not supported Some user defined event listeners on the ancestor of
+ * zr dom call `stopPropagation`.
+ * In these cases, the `mousemove` event might be keep triggered event
+ * if the mouse is released. We try to reduce the side-effect in those cases.
+ * That is, do nothing (especially, `findHover`) in those cases. See `isOutsideBoundary`.
+ *
+ * Note:
+ * If `HandlerProxy` listens to `document` with `useCapture`, `HandlerProxy` needs to
+ * make sure `stopPropagation` and `preventDefault` doing nothing if and only if the event
+ * target is not zrender dom. Becuase it is dangerous to enable users to call them in
+ * `document` capture phase to prevent the propagation to any listener of the webpage.
+ * But they are needed to work when the pointer inside the zrender dom.
+ */
+
+
 var SILENT = 'silent';
 
 function makeEventPacket(eveType, targetInfo, event) {
@@ -2047,17 +2141,19 @@ function makeEventPacket(eveType, targetInfo, event) {
     };
 }
 
-function stopEvent(event) {
+function stopEvent() {
     stop(this.event);
 }
 
 function EmptyProxy() {}
 EmptyProxy.prototype.dispose = function () {};
 
+
 var handlerNames = [
     'click', 'dblclick', 'mousewheel', 'mouseout',
     'mouseup', 'mousedown', 'mousemove', 'contextmenu'
 ];
+
 /**
  * @alias module:zrender/Handler
  * @constructor
@@ -2114,7 +2210,6 @@ var Handler = function (storage, painter, proxy, painterRoot) {
      */
     this._gestureMgr;
 
-
     Draggable.call(this);
 
     this.setHandlerProxy(proxy);
@@ -2143,6 +2238,8 @@ Handler.prototype = {
         var x = event.zrX;
         var y = event.zrY;
 
+        var isOutside = isOutsideBoundary(this, x, y);
+
         var lastHovered = this._hovered;
         var lastHoveredTarget = lastHovered.target;
 
@@ -2155,7 +2252,7 @@ Handler.prototype = {
             lastHoveredTarget = lastHovered.target;
         }
 
-        var hovered = this._hovered = this.findHover(x, y);
+        var hovered = this._hovered = isOutside ? {x: x, y: y} : this.findHover(x, y);
         var hoveredTarget = hovered.target;
 
         var proxy = this.proxy;
@@ -2176,23 +2273,18 @@ Handler.prototype = {
     },
 
     mouseout: function (event) {
-        this.dispatchToElement(this._hovered, 'mouseout', event);
+        var eventControl = event.zrEventControl;
+        var zrIsToLocalDOM = event.zrIsToLocalDOM;
 
-        // There might be some doms created by upper layer application
-        // at the same level of painter.getViewportRoot() (e.g., tooltip
-        // dom created by echarts), where 'globalout' event should not
-        // be triggered when mouse enters these doms. (But 'mouseout'
-        // should be triggered at the original hovered element as usual).
-        var element = event.toElement || event.relatedTarget;
-        var innerDom;
-        do {
-            element = element && element.parentNode;
+        if (eventControl !== 'only_globalout') {
+            this.dispatchToElement(this._hovered, 'mouseout', event);
         }
-        while (element && element.nodeType !== 9 && !(
-            innerDom = element === this.painterRoot
-        ));
 
-        !innerDom && this.trigger('globalout', {event: event});
+        if (eventControl !== 'no_globalout') {
+            // FIXME: if the pointer moving from the extra doms to realy "outside",
+            // the `globalout` should have been triggered. But currently not.
+            !zrIsToLocalDOM && this.trigger('globalout', {type: 'globalout', event: event});
+        }
     },
 
     /**
@@ -2338,9 +2430,18 @@ Handler.prototype = {
 // Common handlers
 each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
     Handler.prototype[name] = function (event) {
-        // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
-        var hovered = this.findHover(event.zrX, event.zrY);
-        var hoveredTarget = hovered.target;
+        var x = event.zrX;
+        var y = event.zrY;
+        var isOutside = isOutsideBoundary(this, x, y);
+
+        var hovered;
+        var hoveredTarget;
+
+        if (name !== 'mouseup' || !isOutside) {
+            // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
+            hovered = this.findHover(x, y);
+            hoveredTarget = hovered.target;
+        }
 
         if (name === 'mousedown') {
             this._downEl = hoveredTarget;
@@ -2392,6 +2493,14 @@ function isHover(displayable, x, y) {
     return false;
 }
 
+/**
+ * See [Drag outside].
+ */
+function isOutsideBoundary(handlerInstance, x, y) {
+    var painter = handlerInstance.painter;
+    return x < 0 || x > painter.getWidth() || y < 0 || y > painter.getHeight();
+}
+
 mixin(Handler, Eventful);
 mixin(Handler, Draggable);
 
@@ -10511,30 +10620,55 @@ Animation.prototype = {
 
 mixin(Animation, Eventful);
 
+/* global document */
+
 var TOUCH_CLICK_DELAY = 300;
 
-var mouseHandlerNames = [
-    'click', 'dblclick', 'mousewheel', 'mouseout',
-    'mouseup', 'mousedown', 'mousemove', 'contextmenu'
-];
+var globalEventSupported = env$1.domSupported;
 
-var touchHandlerNames = [
-    'touchstart', 'touchend', 'touchmove'
-];
 
-var pointerEventNames = {
-    pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
+var localNativeListenerNames = (function () {
+    var mouseHandlerNames = [
+        'click', 'dblclick', 'mousewheel', 'mouseout',
+        'mouseup', 'mousedown', 'mousemove', 'contextmenu'
+    ];
+    var touchHandlerNames = [
+        'touchstart', 'touchend', 'touchmove'
+    ];
+    var pointerEventNameMap = {
+        pointerdown: 1, pointerup: 1, pointermove: 1, pointerout: 1
+    };
+    var pointerHandlerNames = map(mouseHandlerNames, function (name) {
+        var nm = name.replace('mouse', 'pointer');
+        return pointerEventNameMap.hasOwnProperty(nm) ? nm : name;
+    });
+
+    return {
+        mouse: mouseHandlerNames,
+        touch: touchHandlerNames,
+        pointer: pointerHandlerNames
+    };
+})();
+
+var globalNativeListenerNames = {
+    mouse: ['mousemove', 'mouseup'],
+    pointer: ['pointermove', 'pointerup']
 };
 
-var pointerHandlerNames = map(mouseHandlerNames, function (name) {
-    var nm = name.replace('mouse', 'pointer');
-    return pointerEventNames[nm] ? nm : name;
-});
 
 function eventNameFix(name) {
     return (name === 'mousewheel' && env$1.browser.firefox) ? 'DOMMouseScroll' : name;
 }
 
+function isPointerFromTouch(event) {
+    var pointerType = event.pointerType;
+    return pointerType === 'pen' || pointerType === 'touch';
+}
+
+// function useMSGuesture(handlerProxy, event) {
+//     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
+// }
+
 // function onMSGestureChange(proxy, event) {
 //     if (event.translationX || event.translationY) {
 //         // mousemove is carried by MSGesture to reduce the sensitivity.
@@ -10554,117 +10688,174 @@ function eventNameFix(name) {
  * 1. Mobile browsers dispatch mouse events 300ms after touchend.
  * 2. Chrome for Android dispatch mousedown for long-touch about 650ms
  * Result: Blocking Mouse Events for 700ms.
+ *
+ * @param {DOMHandlerScope} scope
  */
-function setTouchTimer(instance) {
-    instance._touching = true;
-    clearTimeout(instance._touchTimer);
-    instance._touchTimer = setTimeout(function () {
-        instance._touching = false;
+function setTouchTimer(scope) {
+    scope.touching = true;
+    if (scope.touchTimer != null) {
+        clearTimeout(scope.touchTimer);
+        scope.touchTimer = null;
+    }
+    scope.touchTimer = setTimeout(function () {
+        scope.touching = false;
+        scope.touchTimer = null;
     }, 700);
 }
 
+// Mark touch, which is useful in distinguish touch and
+// mouse event in upper applicatoin.
+function markTouch(event) {
+    event && (event.zrByTouch = true);
+}
+
+
+// function markTriggeredFromLocal(event) {
+//     event && (event.__zrIsFromLocal = true);
+// }
+
+// function isTriggeredFromLocal(instance, event) {
+//     return !!(event && event.__zrIsFromLocal);
+// }
+
+function normalizeGlobalEvent(instance, event) {
+    // offsetX, offsetY still need to be calculated. They are necessary in the event
+    // handlers of the upper applications. Set `true` to force calculate them.
+    return normalizeEvent(instance.dom, new FakeGlobalEvent(instance, event), true);
+}
+
+/**
+ * Detect whether the given el is in `painterRoot`.
+ */
+function isLocalEl(instance, el) {
+    var isLocal = false;
+    do {
+        el = el && el.parentNode;
+    }
+    while (el && el.nodeType !== 9 && !(
+        isLocal = el === instance.painterRoot
+    ));
+    return isLocal;
+}
+
+/**
+ * Make a fake event but not change the original event,
+ * becuase the global event probably be used by other
+ * listeners not belonging to zrender.
+ * @class
+ */
+function FakeGlobalEvent(instance, event) {
+    this.type = event.type;
+    this.target = this.currentTarget = instance.dom;
+    this.pointerType = event.pointerType;
+    // Necessray for the force calculation of zrX, zrY
+    this.clientX = event.clientX;
+    this.clientY = event.clientY;
+    // Because we do not mount global listeners to touch events,
+    // we do not copy `targetTouches` and `changedTouches` here.
+}
+var fakeGlobalEventProto = FakeGlobalEvent.prototype;
+// we make the default methods on the event do nothing,
+// otherwise it is dangerous. See more details in
+// [Drag outside] in `Handler.js`.
+fakeGlobalEventProto.stopPropagation =
+    fakeGlobalEventProto.stopImmediatePropagation =
+    fakeGlobalEventProto.preventDefault = noop;
+
+
+/**
+ * Local DOM Handlers
+ * @this {HandlerProxy}
+ */
+var localDOMHandlers = {
+
+    mousedown: function (event) {
+        event = normalizeEvent(this.dom, event);
+
+        this._mayPointerCapture = [event.zrX, event.zrY];
+
+        this.trigger('mousedown', event);
+    },
 
-var domHandlers = {
-    /**
-     * Mouse move handler
-     * @inner
-     * @param {Event} event
-     */
     mousemove: function (event) {
         event = normalizeEvent(this.dom, event);
 
+        var downPoint = this._mayPointerCapture;
+        if (downPoint && (event.zrX !== downPoint[0] || event.zrY !== downPoint[1])) {
+            togglePointerCapture(this, true);
+        }
+
         this.trigger('mousemove', event);
     },
 
-    /**
-     * Mouse out handler
-     * @inner
-     * @param {Event} event
-     */
-    mouseout: function (event) {
+    mouseup: function (event) {
         event = normalizeEvent(this.dom, event);
 
-        var element = event.toElement || event.relatedTarget;
-        if (element !== this.dom) {
-            while (element && element.nodeType !== 9) {
-                // 忽略包含在root中的dom引起的mouseOut
-                if (element === this.dom) {
-                    return;
-                }
+        togglePointerCapture(this, false);
 
-                element = element.parentNode;
-            }
+        this.trigger('mouseup', event);
+    },
+
+    mouseout: function (event) {
+        event = normalizeEvent(this.dom, event);
+
+        // Similarly to the browser did on `document` and touch event,
+        // `globalout` will be delayed to final pointer cature release.
+        if (this._pointerCapturing) {
+            event.zrEventControl = 'no_globalout';
         }
 
+        // There might be some doms created by upper layer application
+        // at the same level of painter.getViewportRoot() (e.g., tooltip
+        // dom created by echarts), where 'globalout' event should not
+        // be triggered when mouse enters these doms. (But 'mouseout'
+        // should be triggered at the original hovered element as usual).
+        var element = event.toElement || event.relatedTarget;
+        event.zrIsToLocalDOM = isLocalEl(this, element);
+
         this.trigger('mouseout', event);
     },
 
-    /**
-     * Touch开始响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchstart: function (event) {
         // Default mouse behaviour should not be disabled here.
         // For example, page may needs to be slided.
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this._lastTouchMoment = new Date();
 
         this.handler.processGesture(event, 'start');
 
-        // In touch device, trigger `mousemove`(`mouseover`) should
-        // be triggered, and must before `mousedown` triggered.
-        domHandlers.mousemove.call(this, event);
-
-        domHandlers.mousedown.call(this, event);
-
-        setTouchTimer(this);
+        // For consistent event listener for both touch device and mouse device,
+        // we simulate "mouseover-->mousedown" in touch device. So we trigger
+        // `mousemove` here (to trigger `mouseover` inside), and then trigger
+        // `mousedown`.
+        localDOMHandlers.mousemove.call(this, event);
+        localDOMHandlers.mousedown.call(this, event);
     },
 
-    /**
-     * Touch移动响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchmove: function (event) {
-
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this.handler.processGesture(event, 'change');
 
         // Mouse move should always be triggered no matter whether
         // there is gestrue event, because mouse move and pinch may
         // be used at the same time.
-        domHandlers.mousemove.call(this, event);
-
-        setTouchTimer(this);
+        localDOMHandlers.mousemove.call(this, event);
     },
 
-    /**
-     * Touch结束响应函数
-     * @inner
-     * @param {Event} event
-     */
     touchend: function (event) {
-
         event = normalizeEvent(this.dom, event);
 
-        // Mark touch, which is useful in distinguish touch and
-        // mouse event in upper applicatoin.
-        event.zrByTouch = true;
+        markTouch(event);
 
         this.handler.processGesture(event, 'end');
 
-        domHandlers.mouseup.call(this, event);
+        localDOMHandlers.mouseup.call(this, event);
 
         // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is
         // triggered in `touchstart`. This seems to be illogical, but by this mechanism,
@@ -10677,14 +10868,12 @@ var domHandlers = {
         // click event should always be triggered no matter whether
         // there is gestrue event. System click can not be prevented.
         if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) {
-            domHandlers.click.call(this, event);
+            localDOMHandlers.click.call(this, event);
         }
-
-        setTouchTimer(this);
     },
 
     pointerdown: function (event) {
-        domHandlers.mousedown.call(this, event);
+        localDOMHandlers.mousedown.call(this, event);
 
         // if (useMSGuesture(this, event)) {
         //     this._msGesture.addPointer(event.pointerId);
@@ -10698,12 +10887,12 @@ var domHandlers = {
         // upper application. So, we dont support mousemove on MS touch
         // device yet.
         if (!isPointerFromTouch(event)) {
-            domHandlers.mousemove.call(this, event);
+            localDOMHandlers.mousemove.call(this, event);
         }
     },
 
     pointerup: function (event) {
-        domHandlers.mouseup.call(this, event);
+        localDOMHandlers.mouseup.call(this, event);
     },
 
     pointerout: function (event) {
@@ -10711,80 +10900,77 @@ var domHandlers = {
         // (IE11+/Edge on MS Surface) after click event triggered,
         // which is inconsistent with the mousout behavior we defined
         // in touchend. So we unify them.
-        // (check domHandlers.touchend for detailed explanation)
+        // (check localDOMHandlers.touchend for detailed explanation)
         if (!isPointerFromTouch(event)) {
-            domHandlers.mouseout.call(this, event);
+            localDOMHandlers.mouseout.call(this, event);
         }
     }
-};
-
-function isPointerFromTouch(event) {
-    var pointerType = event.pointerType;
-    return pointerType === 'pen' || pointerType === 'touch';
-}
 
-// function useMSGuesture(handlerProxy, event) {
-//     return isPointerFromTouch(event) && !!handlerProxy._msGesture;
-// }
+};
 
-// Common handlers
-each$1(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
-    domHandlers[name] = function (event) {
+/**
+ * Othere DOM UI Event handlers for zr dom.
+ * @this {HandlerProxy}
+ */
+each$1(['click', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
+    localDOMHandlers[name] = function (event) {
         event = normalizeEvent(this.dom, event);
         this.trigger(name, event);
     };
 });
 
+
 /**
- * 为控制类实例初始化dom 事件处理函数
+ * DOM UI Event handlers for global page.
  *
- * @inner
- * @param {module:zrender/Handler} instance 控制类实例
+ * [Caution]:
+ * those handlers should both support in capture phase and bubble phase!
+ *
+ * @this {HandlerProxy}
  */
-function initDomHandler(instance) {
-    each$1(touchHandlerNames, function (name) {
-        instance._handlers[name] = bind(domHandlers[name], instance);
-    });
+var globalDOMHandlers = {
 
-    each$1(pointerHandlerNames, function (name) {
-        instance._handlers[name] = bind(domHandlers[name], instance);
-    });
+    pointermove: function (event) {
+        // FIXME
+        // pointermove is so sensitive that it always triggered when
+        // tap(click) on touch screen, which affect some judgement in
+        // upper application. So, we dont support mousemove on MS touch
+        // device yet.
+        if (!isPointerFromTouch(event)) {
+            globalDOMHandlers.mousemove.call(this, event);
+        }
+    },
 
-    each$1(mouseHandlerNames, function (name) {
-        instance._handlers[name] = makeMouseHandler(domHandlers[name], instance);
-    });
+    pointerup: function (event) {
+        globalDOMHandlers.mouseup.call(this, event);
+    },
 
-    function makeMouseHandler(fn, instance) {
-        return function () {
-            if (instance._touching) {
-                return;
-            }
-            return fn.apply(instance, arguments);
-        };
-    }
-}
+    mousemove: function (event) {
+        this.trigger('mousemove', event);
+    },
 
+    mouseup: function (event) {
+        var pointerCaptureReleasing = this._pointerCapturing;
 
-function HandlerDomProxy(dom) {
-    Eventful.call(this);
+        togglePointerCapture(this, false);
 
-    this.dom = dom;
+        this.trigger('mouseup', event);
 
-    /**
-     * @private
-     * @type {boolean}
-     */
-    this._touching = false;
+        if (pointerCaptureReleasing) {
+            event.zrEventControl = 'only_globalout';
+            this.trigger('mouseout', event);
+        }
+    }
 
-    /**
-     * @private
-     * @type {number}
-     */
-    this._touchTimer;
+};
 
-    this._handlers = {};
 
-    initDomHandler(this);
+/**
+ * @param {HandlerProxy} instance
+ * @param {DOMHandlerScope} scope
+ */
+function mountLocalDOMEventListeners(instance, scope) {
+    var domHandlers = scope.domHandlers;
 
     if (env$1.pointerEventsSupported) { // Only IE11+/Edge
         // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240),
@@ -10793,7 +10979,12 @@ function HandlerDomProxy(dom) {
         // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on
         // screen, which do not occurs in pointer event.
         // So we use pointer event to both detect touch gesture and mouse behavior.
-        mountHandlers(pointerHandlerNames, this);
+        each$1(localNativeListenerNames.pointer, function (nativeEventName) {
+            mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                // markTriggeredFromLocal(event);
+                domHandlers[nativeEventName].call(instance, event);
+            });
+        });
 
         // FIXME
         // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable,
@@ -10812,7 +11003,13 @@ function HandlerDomProxy(dom) {
     }
     else {
         if (env$1.touchEventsSupported) {
-            mountHandlers(touchHandlerNames, this);
+            each$1(localNativeListenerNames.touch, function (nativeEventName) {
+                mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                    // markTriggeredFromLocal(event);
+                    domHandlers[nativeEventName].call(instance, event);
+                    setTouchTimer(scope);
+                });
+            });
             // Handler of 'mouseout' event is needed in touch mode, which will be mounted below.
             // addEventListener(root, 'mouseout', this._mouseoutHandler);
         }
@@ -10822,23 +11019,145 @@ function HandlerDomProxy(dom) {
         // mouse event can not be handle in those devices.
         // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent
         // mouseevent after touch event triggered, see `setTouchTimer`.
-        mountHandlers(mouseHandlerNames, this);
+        each$1(localNativeListenerNames.mouse, function (nativeEventName) {
+            mountSingleDOMEventListener(scope, nativeEventName, function (event) {
+                event = getNativeEvent(event);
+                if (!scope.touching) {
+                    // markTriggeredFromLocal(event);
+                    domHandlers[nativeEventName].call(instance, event);
+                }
+            });
+        });
+    }
+}
+
+/**
+ * @param {HandlerProxy} instance
+ * @param {DOMHandlerScope} scope
+ */
+function mountGlobalDOMEventListeners(instance, scope) {
+    // Only IE11+/Edge. See the comment in `mountLocalDOMEventListeners`.
+    if (env$1.pointerEventsSupported) {
+        each$1(globalNativeListenerNames.pointer, mount);
+    }
+    // Touch event has implemented "drag outside" so we do not mount global listener for touch event.
+    // (see https://www.w3.org/TR/touch-events/#the-touchmove-event)
+    // We do not consider "both-support-touch-and-mouse device" for this feature (see the comment of
+    // `mountLocalDOMEventListeners`) to avoid bugs util some requirements come.
+    else if (!env$1.touchEventsSupported) {
+        each$1(globalNativeListenerNames.mouse, mount);
+    }
+
+    function mount(nativeEventName) {
+        function nativeEventListener(event) {
+            event = getNativeEvent(event);
+            // See the reason in [Drag outside] in `Handler.js`
+            // This checking supports both `useCapture` or not.
+            // PENDING: if there is performance issue in some devices,
+            // we probably can not use `useCapture` and change a easier
+            // to judes whether local (mark).
+            if (!isLocalEl(instance, event.target)) {
+                event = normalizeGlobalEvent(instance, event);
+                scope.domHandlers[nativeEventName].call(instance, event);
+            }
+        }
+        mountSingleDOMEventListener(
+            scope, nativeEventName, nativeEventListener,
+            {capture: true} // See [Drag Outside] in `Handler.js`
+        );
+    }
+}
+
+function mountSingleDOMEventListener(scope, nativeEventName, listener, opt) {
+    scope.mounted[nativeEventName] = listener;
+    scope.listenerOpts[nativeEventName] = opt;
+    addEventListener(scope.domTarget, eventNameFix(nativeEventName), listener, opt);
+}
+
+function unmountDOMEventListeners(scope) {
+    var mounted = scope.mounted;
+    for (var nativeEventName in mounted) {
+        if (mounted.hasOwnProperty(nativeEventName)) {
+            removeEventListener(
+                scope.domTarget, eventNameFix(nativeEventName), mounted[nativeEventName],
+                scope.listenerOpts[nativeEventName]
+            );
+        }
+    }
+    scope.mounted = {};
+}
+
+/**
+ * See [Drag Outside] in `Handler.js`.
+ * @implement
+ * @param {boolean} isPointerCapturing Should never be `null`/`undefined`.
+ *        `true`: start to capture pointer if it is not capturing.
+ *        `false`: end the capture if it is capturing.
+ */
+function togglePointerCapture(instance, isPointerCapturing) {
+    instance._mayPointerCapture = null;
+
+    if (globalEventSupported && (instance._pointerCapturing ^ isPointerCapturing)) {
+        instance._pointerCapturing = isPointerCapturing;
+
+        var globalHandlerScope = instance._globalHandlerScope;
+        isPointerCapturing
+            ? mountGlobalDOMEventListeners(instance, globalHandlerScope)
+            : unmountDOMEventListeners(globalHandlerScope);
     }
+}
+
+/**
+ * @inner
+ * @class
+ */
+function DOMHandlerScope(domTarget, domHandlers) {
+    this.domTarget = domTarget;
+    this.domHandlers = domHandlers;
+
+    // Key: eventName, value: mounted handler funcitons.
+    // Used for unmount.
+    this.mounted = {};
+    this.listenerOpts = {};
+
+    this.touchTimer = null;
+    this.touching = false;
+}
+
+/**
+ * @public
+ * @class
+ */
+function HandlerDomProxy(dom, painterRoot) {
+    Eventful.call(this);
+
+    this.dom = dom;
+    this.painterRoot = painterRoot;
 
-    function mountHandlers(handlerNames, instance) {
-        each$1(handlerNames, function (name) {
-            addEventListener(dom, eventNameFix(name), instance._handlers[name]);
-        }, instance);
+    this._localHandlerScope = new DOMHandlerScope(dom, localDOMHandlers);
+
+    if (globalEventSupported) {
+        this._globalHandlerScope = new DOMHandlerScope(document, globalDOMHandlers);
     }
+
+    /**
+     * @type {boolean}
+     */
+    this._pointerCapturing = false;
+    /**
+     * @type {Array.<number>} [x, y] or null.
+     */
+    this._mayPointerCapture = null;
+
+    mountLocalDOMEventListeners(this, this._localHandlerScope);
 }
 
 var handlerDomProxyProto = HandlerDomProxy.prototype;
-handlerDomProxyProto.dispose = function () {
-    var handlerNames = mouseHandlerNames.concat(touchHandlerNames);
 
-    for (var i = 0; i < handlerNames.length; i++) {
-        var name = handlerNames[i];
-        removeEventListener(this.dom, eventNameFix(name), this._handlers[name]);
+handlerDomProxyProto.dispose = function () {
+    unmountDOMEventListeners(this._localHandlerScope);
+    if (globalEventSupported) {
+        unmountDOMEventListeners(this._globalHandlerScope);
     }
 };
 
@@ -10846,6 +11165,7 @@ handlerDomProxyProto.setCursor = function (cursorStyle) {
     this.dom.style && (this.dom.style.cursor = cursorStyle || 'default');
 };
 
+
 mixin(HandlerDomProxy, Eventful);
 
 /*!
@@ -10867,7 +11187,7 @@ var painterCtors = {
 /**
  * @type {string}
  */
-var version$1 = '4.1.2';
+var version$1 = '4.2.0';
 
 /**
  * Initializing a zrender instance
@@ -10946,7 +11266,7 @@ var ZRender = function (id, dom, opts) {
     this.storage = storage;
     this.painter = painter;
 
-    var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot()) : null;
+    var handerProxy = (!env$1.node && !env$1.worker) ? new HandlerDomProxy(painter.getViewportRoot(), painter.root) : null;
     this.handler = new Handler(storage, painter, handerProxy, painter.root);
 
     /**
@@ -17425,8 +17745,7 @@ function rollbackDefaultTextStyle(style) {
 }
 
 function getFont(opt, ecModel) {
-    // ecModel or default text style model.
-    var gTextStyleModel = ecModel || ecModel.getModel('textStyle');
+    var gTextStyleModel = ecModel && ecModel.getModel('textStyle');
     return trim([
         // FIXME in node-canvas fontWeight is before fontStyle
         opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '',
@@ -19946,178 +20265,6 @@ var colorPaletteMixin = {
 * under the License.
 */
 
-/**
- * Helper for model references.
- * There are many manners to refer axis/coordSys.
- */
-
-// TODO
-// merge relevant logic to this file?
-// check: "modelHelper" of tooltip and "BrushTargetManager".
-
-/**
- * @return {Object} For example:
- * {
- *     coordSysName: 'cartesian2d',
- *     coordSysDims: ['x', 'y', ...],
- *     axisMap: HashMap({
- *         x: xAxisModel,
- *         y: yAxisModel
- *     }),
- *     categoryAxisMap: HashMap({
- *         x: xAxisModel,
- *         y: undefined
- *     }),
- *     // It also indicate that whether there is category axis.
- *     firstCategoryDimIndex: 1,
- *     // To replace user specified encode.
- * }
- */
-function getCoordSysDefineBySeries(seriesModel) {
-    var coordSysName = seriesModel.get('coordinateSystem');
-    var result = {
-        coordSysName: coordSysName,
-        coordSysDims: [],
-        axisMap: createHashMap(),
-        categoryAxisMap: createHashMap()
-    };
-    var fetch = fetchers[coordSysName];
-    if (fetch) {
-        fetch(seriesModel, result, result.axisMap, result.categoryAxisMap);
-        return result;
-    }
-}
-
-var fetchers = {
-
-    cartesian2d: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var xAxisModel = seriesModel.getReferringComponents('xAxis')[0];
-        var yAxisModel = seriesModel.getReferringComponents('yAxis')[0];
-
-        if (__DEV__) {
-            if (!xAxisModel) {
-                throw new Error('xAxis "' + retrieve(
-                    seriesModel.get('xAxisIndex'),
-                    seriesModel.get('xAxisId'),
-                    0
-                ) + '" not found');
-            }
-            if (!yAxisModel) {
-                throw new Error('yAxis "' + retrieve(
-                    seriesModel.get('xAxisIndex'),
-                    seriesModel.get('yAxisId'),
-                    0
-                ) + '" not found');
-            }
-        }
-
-        result.coordSysDims = ['x', 'y'];
-        axisMap.set('x', xAxisModel);
-        axisMap.set('y', yAxisModel);
-
-        if (isCategory(xAxisModel)) {
-            categoryAxisMap.set('x', xAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-        if (isCategory(yAxisModel)) {
-            categoryAxisMap.set('y', yAxisModel);
-            result.firstCategoryDimIndex = 1;
-        }
-    },
-
-    singleAxis: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var singleAxisModel = seriesModel.getReferringComponents('singleAxis')[0];
-
-        if (__DEV__) {
-            if (!singleAxisModel) {
-                throw new Error('singleAxis should be specified.');
-            }
-        }
-
-        result.coordSysDims = ['single'];
-        axisMap.set('single', singleAxisModel);
-
-        if (isCategory(singleAxisModel)) {
-            categoryAxisMap.set('single', singleAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-    },
-
-    polar: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var polarModel = seriesModel.getReferringComponents('polar')[0];
-        var radiusAxisModel = polarModel.findAxisModel('radiusAxis');
-        var angleAxisModel = polarModel.findAxisModel('angleAxis');
-
-        if (__DEV__) {
-            if (!angleAxisModel) {
-                throw new Error('angleAxis option not found');
-            }
-            if (!radiusAxisModel) {
-                throw new Error('radiusAxis option not found');
-            }
-        }
-
-        result.coordSysDims = ['radius', 'angle'];
-        axisMap.set('radius', radiusAxisModel);
-        axisMap.set('angle', angleAxisModel);
-
-        if (isCategory(radiusAxisModel)) {
-            categoryAxisMap.set('radius', radiusAxisModel);
-            result.firstCategoryDimIndex = 0;
-        }
-        if (isCategory(angleAxisModel)) {
-            categoryAxisMap.set('angle', angleAxisModel);
-            result.firstCategoryDimIndex = 1;
-        }
-    },
-
-    geo: function (seriesModel, result, axisMap, categoryAxisMap) {
-        result.coordSysDims = ['lng', 'lat'];
-    },
-
-    parallel: function (seriesModel, result, axisMap, categoryAxisMap) {
-        var ecModel = seriesModel.ecModel;
-        var parallelModel = ecModel.getComponent(
-            'parallel', seriesModel.get('parallelIndex')
-        );
-        var coordSysDims = result.coordSysDims = parallelModel.dimensions.slice();
-
-        each$1(parallelModel.parallelAxisIndex, function (axisIndex, index) {
-            var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
-            var axisDim = coordSysDims[index];
-            axisMap.set(axisDim, axisModel);
-
-            if (isCategory(axisModel) && result.firstCategoryDimIndex == null) {
-                categoryAxisMap.set(axisDim, axisModel);
-                result.firstCategoryDimIndex = index;
-            }
-        });
-    }
-};
-
-function isCategory(axisModel) {
-    return axisModel.get('type') === 'category';
-}
-
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
 // Avoid typo.
 var SOURCE_FORMAT_ORIGINAL = 'original';
 var SOURCE_FORMAT_ARRAY_ROWS = 'arrayRows';
@@ -20287,6 +20434,13 @@ enableClassCheck(Source);
 * under the License.
 */
 
+// The result of `guessOrdinal`.
+var BE_ORDINAL = {
+    Must: 1, // Encounter string but not '-' and not number-like.
+    Might: 2, // Encounter string but number-like.
+    Not: 3 // Other cases
+};
+
 var inner$3 = makeInner();
 
 /**
@@ -20420,14 +20574,6 @@ function prepareSource(seriesModel) {
         data, sourceFormat, seriesLayoutBy, sourceHeader, dimensionsDefine
     );
 
-    // Note: dataset option does not have `encode`.
-    var encodeDefine = seriesOption.encode;
-    if (!encodeDefine && datasetModel) {
-        encodeDefine = makeDefaultEncode(
-            seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
-        );
-    }
-
     inner$3(seriesModel).source = new Source({
         data: data,
         fromDataset: fromDataset,
@@ -20436,7 +20582,8 @@ function prepareSource(seriesModel) {
         dimensionsDefine: completeResult.dimensionsDefine,
         startIndex: completeResult.startIndex,
         dimensionsDetectCount: completeResult.dimensionsDetectCount,
-        encodeDefine: encodeDefine
+        // Note: dataset option does not have `encode`.
+        encodeDefine: seriesOption.encode
     });
 }
 
@@ -20448,7 +20595,6 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
 
     var dimensionsDetectCount;
     var startIndex;
-    var findPotentialName;
 
     if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
         // Rule: Most of the first line are string: it is header.
@@ -20491,13 +20637,11 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
     else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
         if (!dimensionsDefine) {
             dimensionsDefine = objectRowsCollectDimensions(data);
-            findPotentialName = true;
         }
     }
     else if (sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS) {
         if (!dimensionsDefine) {
             dimensionsDefine = [];
-            findPotentialName = true;
             each$1(data, function (colArr, key) {
                 dimensionsDefine.push(key);
             });
@@ -20513,21 +20657,10 @@ function completeBySourceData(data, sourceFormat, seriesLayoutBy, sourceHeader,
         }
     }
 
-    var potentialNameDimIndex;
-    if (findPotentialName) {
-        each$1(dimensionsDefine, function (dim, idx) {
-            if ((isObject$1(dim) ? dim.name : dim) === 'name') {
-                potentialNameDimIndex = idx;
-            }
-        });
-    }
-
     return {
         startIndex: startIndex,
         dimensionsDefine: normalizeDimensionsDefine(dimensionsDefine),
-        dimensionsDetectCount: dimensionsDetectCount,
-        potentialNameDimIndex: potentialNameDimIndex
-        // TODO: potentialIdDimIdx
+        dimensionsDetectCount: dimensionsDetectCount
     };
 }
 
@@ -20601,101 +20734,100 @@ function objectRowsCollectDimensions(data) {
     }
 }
 
-// ??? TODO merge to completedimensions, where also has
-// default encode making logic. And the default rule
-// should depends on series? consider 'map'.
-function makeDefaultEncode(
-    seriesModel, datasetModel, data, sourceFormat, seriesLayoutBy, completeResult
-) {
-    var coordSysDefine = getCoordSysDefineBySeries(seriesModel);
+/**
+ * [The strategy of the arrengment of data dimensions for dataset]:
+ * "value way": all axes are non-category axes. So series one by one take
+ *     several (the number is coordSysDims.length) dimensions from dataset.
+ *     The result of data arrengment of data dimensions like:
+ *     | ser0_x | ser0_y | ser1_x | ser1_y | ser2_x | ser2_y |
+ * "category way": at least one axis is category axis. So the the first data
+ *     dimension is always mapped to the first category axis and shared by
+ *     all of the series. The other data dimensions are taken by series like
+ *     "value way" does.
+ *     The result of data arrengment of data dimensions like:
+ *     | ser_shared_x | ser0_y | ser1_y | ser2_y |
+ *
+ * @param {Array.<Object|string>} coordDimensions [{name: <string>, type: <string>, dimsDef: <Array>}, ...]
+ * @param {module:model/Series} seriesModel
+ * @param {module:data/Source} source
+ * @return {Object} encode Never be `null/undefined`.
+ */
+function makeSeriesEncodeForAxisCoordSys(coordDimensions, seriesModel, source) {
     var encode = {};
-    // var encodeTooltip = [];
-    // var encodeLabel = [];
+
+    var datasetModel = getDatasetModel(seriesModel);
+    // Currently only make default when using dataset, util more reqirements occur.
+    if (!datasetModel || !coordDimensions) {
+        return encode;
+    }
+
     var encodeItemName = [];
     var encodeSeriesName = [];
-    var seriesType = seriesModel.subType;
-
-    // ??? TODO refactor: provide by series itself.
-    // Consider the case: 'map' series is based on geo coordSys,
-    // 'graph', 'heatmap' can be based on cartesian. But can not
-    // give default rule simply here.
-    var nSeriesMap = createHashMap(['pie', 'map', 'funnel']);
-    var cSeriesMap = createHashMap([
-        'line', 'bar', 'pictorialBar', 'scatter', 'effectScatter', 'candlestick', 'boxplot'
-    ]);
-
-    // Usually in this case series will use the first data
-    // dimension as the "value" dimension, or other default
-    // processes respectively.
-    if (coordSysDefine && cSeriesMap.get(seriesType) != null) {
-        var ecModel = seriesModel.ecModel;
-        var datasetMap = inner$3(ecModel).datasetMap;
-        var key = datasetModel.uid + '_' + seriesLayoutBy;
-        var datasetRecord = datasetMap.get(key)
-            || datasetMap.set(key, {categoryWayDim: 1, valueWayDim: 0});
 
-        // TODO
-        // Auto detect first time axis and do arrangement.
-        each$1(coordSysDefine.coordSysDims, function (coordDim) {
-            // In value way.
-            if (coordSysDefine.firstCategoryDimIndex == null) {
-                var dataDim = datasetRecord.valueWayDim++;
-                encode[coordDim] = dataDim;
-
-                // ??? TODO give a better default series name rule?
-                // especially when encode x y specified.
-                // consider: when mutiple series share one dimension
-                // category axis, series name should better use
-                // the other dimsion name. On the other hand, use
-                // both dimensions name.
-
-                encodeSeriesName.push(dataDim);
-                // encodeTooltip.push(dataDim);
-                // encodeLabel.push(dataDim);
-            }
-            // In category way, category axis.
-            else if (coordSysDefine.categoryAxisMap.get(coordDim)) {
-                encode[coordDim] = 0;
-                encodeItemName.push(0);
-            }
-            // In category way, non-category axis.
-            else {
-                var dataDim = datasetRecord.categoryWayDim++;
-                encode[coordDim] = dataDim;
-                // encodeTooltip.push(dataDim);
-                // encodeLabel.push(dataDim);
-                encodeSeriesName.push(dataDim);
-            }
-        });
+    var ecModel = seriesModel.ecModel;
+    var datasetMap = inner$3(ecModel).datasetMap;
+    var key = datasetModel.uid + '_' + source.seriesLayoutBy;
+
+    var baseCategoryDimIndex;
+    var categoryWayValueDimStart;
+    coordDimensions = coordDimensions.slice();
+    each$1(coordDimensions, function (coordDimInfo, coordDimIdx) {
+        !isObject$1(coordDimInfo) && (coordDimensions[coordDimIdx] = {name: coordDimInfo});
+        if (coordDimInfo.type === 'ordinal' && baseCategoryDimIndex == null) {
+            baseCategoryDimIndex = coordDimIdx;
+            categoryWayValueDimStart = getDataDimCountOnCoordDim(coordDimensions[coordDimIdx]);
+        }
+        encode[coordDimInfo.name] = [];
+    });
+
+    var datasetRecord = datasetMap.get(key)
+        || datasetMap.set(key, {categoryWayDim: categoryWayValueDimStart, valueWayDim: 0});
+
+    // TODO
+    // Auto detect first time axis and do arrangement.
+    each$1(coordDimensions, function (coordDimInfo, coordDimIdx) {
+        var coordDimName = coordDimInfo.name;
+        var count = getDataDimCountOnCoordDim(coordDimInfo);
+
+        // In value way.
... 14283 lines suppressed ...


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


[incubator-echarts] 03/03: release: upgrade zrender in package-lock.

Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

sushuang pushed a commit to branch release
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit f4fc1b0625f3c88b9f4dbed03eeb091ed6bd1079
Author: SHUANG SU <su...@gmail.com>
AuthorDate: Thu Dec 12 22:12:03 2019 +0800

    release: upgrade zrender in package-lock.
---
 package-lock.json | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 9312b67..4049034 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "echarts",
-  "version": "4.5.0",
+  "version": "4.6.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2334,9 +2334,9 @@
       "dev": true
     },
     "zrender": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/zrender/-/zrender-4.1.2.tgz",
-      "integrity": "sha512-MJYEo1ZOVesjxYsfcGtPXnUREmh4ACMV08QZLGZ3S7D1xOd96iz3O6nf6pv5PHb5NSHkbizr7ChSIgtAGwncvA=="
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/zrender/-/zrender-4.2.0.tgz",
+      "integrity": "sha512-YJ9hxt5uFincYYU3KK31+Ce+B6PJmYYK0Q9fQ6jOUAoC/VHbe4kCKAPkxKeT7jGTxrK5wYu18R0TLGqj2zbEOA=="
     }
   }
 }


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