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 2018/04/13 11:03:14 UTC

[incubator-echarts] 02/03: (1) Some refactor about axisTick, axisLabel, splitLine, splitArea interval logic: arrange and uniform the API, about 'ifIgnoreOnTick', 'isLabelIgnored', 'getTicksCoords', 'getLabelsCoords', etc. Fixed the inconsistent calling of user specified "axis interval" function. (2) Make the choice of axisTick, axisLabel, splitLine, splitArea be stable when category axis auto interval exists and moving by dataZoom. (3) Make the animation appropriate for label, ticks, splitLine, splitArea when view [...]

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

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

commit 748f33eb24637a2e3822a568ffd3d7ffad0c3c61
Author: sushuang <su...@gmail.com>
AuthorDate: Fri Apr 13 18:50:52 2018 +0800

    (1) Some refactor about axisTick, axisLabel, splitLine, splitArea interval logic: arrange and uniform the API, about 'ifIgnoreOnTick', 'isLabelIgnored', 'getTicksCoords',  'getLabelsCoords', etc. Fixed the inconsistent calling of user specified "axis interval" function.
    (2) Make the choice of axisTick, axisLabel, splitLine, splitArea be stable when category axis auto interval exists and moving by dataZoom.
    (3) Make the animation appropriate for label, ticks, splitLine, splitArea when view window moving (by dataZoom) in category axis.
---
 src/chart/line/LineView.js                         |  17 +-
 src/component/axis/AngleAxisView.js                |  63 +-
 src/component/axis/AxisBuilder.js                  | 110 +--
 src/component/axis/CartesianAxisView.js            | 103 ++-
 src/component/axis/RadiusAxisView.js               |  11 +-
 src/component/axis/SingleAxisView.js               |  25 +-
 src/component/axisPointer/CartesianAxisPointer.js  |   2 +-
 src/component/axisPointer/SingleAxisPointer.js     |   2 +-
 src/component/radar/RadarView.js                   |   8 +-
 src/component/timeline/SliderTimelineView.js       |  52 +-
 src/component/timeline/TimelineAxis.js             |  49 +-
 src/coord/Axis.js                                  | 213 +++---
 src/coord/axisHelper.js                            | 429 +++++++++--
 src/coord/axisModelCommonMixin.js                  |  11 -
 src/coord/cartesian/Axis2D.js                      |  15 -
 src/coord/cartesian/Grid.js                        |  68 +-
 .../cartesian}/cartesianAxisHelper.js              |   8 +-
 src/coord/parallel/Parallel.js                     |   3 +-
 src/coord/single/SingleAxis.js                     |   5 -
 .../axis => coord/single}/singleAxisHelper.js      |   6 +-
 src/layout/points.js                               |   5 +-
 src/scale/Interval.js                              |  12 -
 src/scale/Scale.js                                 |  19 +-
 test/axis-containLabel.html                        | 287 +++++++
 test/axis-interval.html                            |  16 -
 test/axis-interval2.html                           | 851 +++++++++++++++++++++
 test/axis-lastLabel.html                           | 162 +++-
 test/bar.html                                      |   8 +
 test/line.html                                     |  56 +-
 test/timeline-layout.html                          |  16 +-
 30 files changed, 2001 insertions(+), 631 deletions(-)

diff --git a/src/chart/line/LineView.js b/src/chart/line/LineView.js
index 0f5ff8d..663e448 100644
--- a/src/chart/line/LineView.js
+++ b/src/chart/line/LineView.js
@@ -605,15 +605,26 @@ export default ChartView.extend({
         this._polygon = polygon;
         return polygon;
     },
+
     /**
      * @private
      */
     _getSymbolIgnoreFunc: function (data, coordSys) {
         var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
-        // `getLabelInterval` is provided by echarts/component/axis
-        if (categoryAxis && categoryAxis.isLabelIgnored) {
-            return zrUtil.bind(categoryAxis.isLabelIgnored, categoryAxis);
+        if (!categoryAxis) {
+            return;
         }
+
+        var categoryDataDim = data.mapDimension(categoryAxis.dim);
+        var labelMap = {};
+
+        zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
+            labelMap[labelItem.tickValue] = 1;
+        });
+
+        return function (dataIndex) {
+            return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));
+        };
     },
 
     /**
diff --git a/src/component/axis/AngleAxisView.js b/src/component/axis/AngleAxisView.js
index 396451d..aba718b 100644
--- a/src/component/axis/AngleAxisView.js
+++ b/src/component/axis/AngleAxisView.js
@@ -23,6 +23,18 @@ function getRadiusIdx(polar) {
     return radiusAxis.inverse ? 0 : 1;
 }
 
+// Remove the last tick which will overlap the first tick
+function fixAngleOverlap(list) {
+    var firstItem = list[0];
+    var lastItem = list[list.length - 1];
+    if (firstItem
+        && lastItem
+        && Math.abs(Math.abs(firstItem.coord - lastItem.coord) - 360) < 1e-4
+    ) {
+        list.pop();
+    }
+}
+
 export default AxisView.extend({
 
     type: 'angleAxis',
@@ -38,18 +50,22 @@ export default AxisView.extend({
         var angleAxis = angleAxisModel.axis;
         var polar = angleAxis.polar;
         var radiusExtent = polar.getRadiusAxis().getExtent();
+
         var ticksAngles = angleAxis.getTicksCoords();
+        var labels = angleAxis.getViewLabels();
 
-        if (angleAxis.type !== 'category') {
-            // Remove the last tick which will overlap the first tick
-            ticksAngles.pop();
-        }
+        zrUtil.each(labels, function (labelItem) {
+            labelItem.coord = angleAxis.dataToCoord(labelItem.tickValue);
+        });
+
+        fixAngleOverlap(labels);
+        fixAngleOverlap(ticksAngles);
 
         zrUtil.each(elementList, function (name) {
             if (angleAxisModel.get(name +'.show')
                 && (!angleAxis.scale.isBlank() || name === 'axisLine')
             ) {
-                this['_' + name](angleAxisModel, polar, ticksAngles, radiusExtent);
+                this['_' + name](angleAxisModel, polar, ticksAngles, radiusExtent, labels);
             }
         }, this);
     },
@@ -84,9 +100,9 @@ export default AxisView.extend({
         var tickLen = (tickModel.get('inside') ? -1 : 1) * tickModel.get('length');
         var radius = radiusExtent[getRadiusIdx(polar)];
 
-        var lines = zrUtil.map(ticksAngles, function (tickAngle) {
+        var lines = zrUtil.map(ticksAngles, function (tickAngleItem) {
             return new graphic.Line({
-                shape: getAxisLineShape(polar, [radius, radius + tickLen], tickAngle)
+                shape: getAxisLineShape(polar, [radius, radius + tickLen], tickAngleItem.coord)
             });
         });
         this.group.add(graphic.mergePath(
@@ -104,24 +120,20 @@ export default AxisView.extend({
     /**
      * @private
      */
-    _axisLabel: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
-        var axis = angleAxisModel.axis;
-
+    _axisLabel: function (angleAxisModel, polar, ticksAngles, radiusExtent, labels) {
         var rawCategoryData = angleAxisModel.getCategories(true);
 
         var commonLabelModel = angleAxisModel.getModel('axisLabel');
-        var labels = angleAxisModel.getFormattedLabels();
 
         var labelMargin = commonLabelModel.get('margin');
-        var labelsAngles = axis.getLabelsCoords();
-        var ticks = axis.scale.getTicks();
 
         // Use length of ticksAngles because it may remove the last tick to avoid overlapping
-        for (var i = 0; i < ticks.length; i++) {
+        zrUtil.each(labels, function (labelItem, idx) {
             var labelModel = commonLabelModel;
-            var tickVal = ticks[i];
+            var tickValue = labelItem.tickValue;
+
             var r = radiusExtent[getRadiusIdx(polar)];
-            var p = polar.coordToPoint([r + labelMargin, labelsAngles[i]]);
+            var p = polar.coordToPoint([r + labelMargin, labelItem.coord]);
             var cx = polar.cx;
             var cy = polar.cy;
 
@@ -130,8 +142,8 @@ export default AxisView.extend({
             var labelTextVerticalAlign = Math.abs(p[1] - cy) / r < 0.3
                 ? 'middle' : (p[1] > cy ? 'top' : 'bottom');
 
-            if (rawCategoryData && rawCategoryData[tickVal] && rawCategoryData[tickVal].textStyle) {
-                labelModel = new Model(rawCategoryData[tickVal].textStyle, commonLabelModel, commonLabelModel.ecModel);
+            if (rawCategoryData && rawCategoryData[tickValue] && rawCategoryData[tickValue].textStyle) {
+                labelModel = new Model(rawCategoryData[tickValue].textStyle, commonLabelModel, commonLabelModel.ecModel);
             }
 
             var textEl = new graphic.Text({silent: true});
@@ -140,11 +152,11 @@ export default AxisView.extend({
                 x: p[0],
                 y: p[1],
                 textFill: labelModel.getTextColor() || angleAxisModel.get('axisLine.lineStyle.color'),
-                text: labels[i],
+                text: labelItem.formattedLabel,
                 textAlign: labelTextAlign,
                 textVerticalAlign: labelTextVerticalAlign
             });
-        }
+        }, this);
     },
 
     /**
@@ -164,7 +176,7 @@ export default AxisView.extend({
             var colorIndex = (lineCount++) % lineColors.length;
             splitLines[colorIndex] = splitLines[colorIndex] || [];
             splitLines[colorIndex].push(new graphic.Line({
-                shape: getAxisLineShape(polar, radiusExtent, ticksAngles[i])
+                shape: getAxisLineShape(polar, radiusExtent, ticksAngles[i].coord)
             }));
         }
 
@@ -185,6 +197,9 @@ export default AxisView.extend({
      * @private
      */
     _splitArea: function (angleAxisModel, polar, ticksAngles, radiusExtent) {
+        if (!ticksAngles.length) {
+            return;
+        }
 
         var splitAreaModel = angleAxisModel.getModel('splitArea');
         var areaStyleModel = splitAreaModel.getModel('areaStyle');
@@ -196,7 +211,7 @@ export default AxisView.extend({
         var splitAreas = [];
 
         var RADIAN = Math.PI / 180;
-        var prevAngle = -ticksAngles[0] * RADIAN;
+        var prevAngle = -ticksAngles[0].coord * RADIAN;
         var r0 = Math.min(radiusExtent[0], radiusExtent[1]);
         var r1 = Math.max(radiusExtent[0], radiusExtent[1]);
 
@@ -212,12 +227,12 @@ export default AxisView.extend({
                     r0: r0,
                     r: r1,
                     startAngle: prevAngle,
-                    endAngle: -ticksAngles[i] * RADIAN,
+                    endAngle: -ticksAngles[i].coord * RADIAN,
                     clockwise: clockwise
                 },
                 silent: true
             }));
-            prevAngle = -ticksAngles[i] * RADIAN;
+            prevAngle = -ticksAngles[i].coord * RADIAN;
         }
 
         // Simple optimization
diff --git a/src/component/axis/AxisBuilder.js b/src/component/axis/AxisBuilder.js
index 0d2f0dc..1e623af 100644
--- a/src/component/axis/AxisBuilder.js
+++ b/src/component/axis/AxisBuilder.js
@@ -53,8 +53,6 @@ function makeAxisEventDataBase(axisModel) {
  * @param {string} [opt.axisName] default get from axisModel.
  * @param {number} [opt.axisNameAvailableWidth]
  * @param {number} [opt.labelRotate] by degree, default get from axisModel.
- * @param {number} [opt.labelInterval] Default label interval when label
- *                                     interval from model is null or 'auto'.
  * @param {number} [opt.strokeContainThreshold] Default label interval when label
  * @param {number} [opt.nameTruncateMaxWidth]
  */
@@ -542,48 +540,6 @@ function isNameLocationCenter(nameLocation) {
     return nameLocation === 'middle' || nameLocation === 'center';
 }
 
-/**
- * @static
- */
-var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick = function (
-    axis,
-    i,
-    interval,
-    ticksCnt,
-    showMinLabel,
-    showMaxLabel
-) {
-    if (i === 0 && showMinLabel || i === ticksCnt - 1 && showMaxLabel) {
-        return false;
-    }
-
-    // FIXME
-    // Have not consider label overlap (if label is too long) yet.
-
-    var rawTick;
-    var scale = axis.scale;
-    return scale.type === 'ordinal'
-        && (
-            typeof interval === 'function'
-                ? (
-                    rawTick = scale.getTicks()[i],
-                    !interval(rawTick, scale.getLabel(rawTick))
-                )
-                : i % (interval + 1)
-        );
-};
-
-/**
- * @static
- */
-var getInterval = AxisBuilder.getInterval = function (model, labelInterval) {
-    var interval = model.get('interval');
-    if (interval == null || interval == 'auto') {
-        interval = labelInterval;
-    }
-    return interval;
-};
-
 function buildAxisTick(axisBuilder, axisModel, opt) {
     var axis = axisModel.axis;
 
@@ -596,14 +552,7 @@ function buildAxisTick(axisBuilder, axisModel, opt) {
     var lineStyleModel = tickModel.getModel('lineStyle');
     var tickLen = tickModel.get('length');
 
-    var tickInterval = getInterval(tickModel, opt.labelInterval);
-    var ticksCoords = axis.getTicksCoords(tickModel.get('alignWithLabel'));
-    // FIXME
-    // Corresponds to ticksCoords ?
-    var ticks = axis.scale.getTicks();
-
-    var showMinLabel = axisModel.get('axisLabel.showMinLabel');
-    var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
+    var ticksCoords = axis.getTicksCoords();
 
     var pt1 = [];
     var pt2 = [];
@@ -611,17 +560,8 @@ function buildAxisTick(axisBuilder, axisModel, opt) {
 
     var tickEls = [];
 
-    var ticksCnt = ticksCoords.length;
-    for (var i = 0; i < ticksCnt; i++) {
-        // Only ordinal scale support tick interval
-        if (ifIgnoreOnTick(
-            axis, i, tickInterval, ticksCnt,
-            showMinLabel, showMaxLabel
-        )) {
-            continue;
-        }
-
-        var tickCoord = ticksCoords[i];
+    for (var i = 0; i < ticksCoords.length; i++) {
+        var tickCoord = ticksCoords[i].coord;
 
         pt1[0] = tickCoord;
         pt1[1] = 0;
@@ -635,7 +575,7 @@ function buildAxisTick(axisBuilder, axisModel, opt) {
         // Tick line, Not use group transform to have better line draw
         var tickEl = new graphic.Line(graphic.subPixelOptimizeLine({
             // Id for animation
-            anid: 'tick_' + ticks[i],
+            anid: 'tick_' + ticksCoords[i].tickValue,
 
             shape: {
                 x1: pt1[0],
@@ -669,8 +609,7 @@ function buildAxisLabel(axisBuilder, axisModel, opt) {
 
     var labelModel = axisModel.getModel('axisLabel');
     var labelMargin = labelModel.get('margin');
-    var ticks = axis.scale.getTicks();
-    var labels = axisModel.getFormattedLabels();
+    var labels = axis.getViewLabels();
 
     // Special label rotate.
     var labelRotation = (
@@ -684,37 +623,30 @@ function buildAxisLabel(axisBuilder, axisModel, opt) {
     var silent = isSilent(axisModel);
     var triggerEvent = axisModel.get('triggerEvent');
 
-    var showMinLabel = axisModel.get('axisLabel.showMinLabel');
-    var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
-
-    each(ticks, function (tickVal, index) {
-        if (ifIgnoreOnTick(
-            axis, index, opt.labelInterval, ticks.length,
-            showMinLabel, showMaxLabel
-        )) {
-            return;
-        }
+    each(labels, function (labelItem, index) {
+        var tickValue = labelItem.tickValue;
+        var formattedLabel = labelItem.formattedLabel;
+        var rawLabel = labelItem.rawLabel;
 
         var itemLabelModel = labelModel;
-        if (rawCategoryData && rawCategoryData[tickVal] && rawCategoryData[tickVal].textStyle) {
+        if (rawCategoryData && rawCategoryData[tickValue] && rawCategoryData[tickValue].textStyle) {
             itemLabelModel = new Model(
-                rawCategoryData[tickVal].textStyle, labelModel, axisModel.ecModel
+                rawCategoryData[tickValue].textStyle, labelModel, axisModel.ecModel
             );
         }
 
         var textColor = itemLabelModel.getTextColor()
             || axisModel.get('axisLine.lineStyle.color');
 
-        var tickCoord = axis.dataToCoord(tickVal);
+        var tickCoord = axis.dataToCoord(tickValue);
         var pos = [
             tickCoord,
             opt.labelOffset + opt.labelDirection * labelMargin
         ];
-        var labelStr = axis.scale.getLabel(tickVal);
 
         var textEl = new graphic.Text({
             // Id for animation
-            anid: 'label_' + tickVal,
+            anid: 'label_' + tickValue,
             position: pos,
             rotation: labelLayout.rotation,
             silent: silent,
@@ -722,7 +654,7 @@ function buildAxisLabel(axisBuilder, axisModel, opt) {
         });
 
         graphic.setTextStyle(textEl.style, itemLabelModel, {
-            text: labels[index],
+            text: formattedLabel,
             textAlign: itemLabelModel.getShallow('align', true)
                 || labelLayout.textAlign,
             textVerticalAlign: itemLabelModel.getShallow('verticalAlign', true)
@@ -733,11 +665,15 @@ function buildAxisLabel(axisBuilder, axisModel, opt) {
                     // (1) In category axis with data zoom, tick is not the original
                     // index of axis.data. So tick should not be exposed to user
                     // in category axis.
-                    // (2) Compatible with previous version, which always returns labelStr.
-                    // But in interval scale labelStr is like '223,445', which maked
-                    // user repalce ','. So we modify it to return original val but remain
+                    // (2) Compatible with previous version, which always use formatted label as
+                    // input. But in interval scale the formatted label is like '223,445', which
+                    // maked user repalce ','. So we modify it to return original val but remain
                     // it as 'string' to avoid error in replacing.
-                    axis.type === 'category' ? labelStr : axis.type === 'value' ? tickVal + '' : tickVal,
+                    axis.type === 'category'
+                        ? rawLabel
+                        : axis.type === 'value'
+                        ? tickValue + ''
+                        : tickValue,
                     index
                 )
                 : textColor
@@ -747,7 +683,7 @@ function buildAxisLabel(axisBuilder, axisModel, opt) {
         if (triggerEvent) {
             textEl.eventData = makeAxisEventDataBase(axisModel);
             textEl.eventData.targetType = 'axisLabel';
-            textEl.eventData.value = labelStr;
+            textEl.eventData.value = rawLabel;
         }
 
         // FIXME
diff --git a/src/component/axis/CartesianAxisView.js b/src/component/axis/CartesianAxisView.js
index 86c5e46..10f60f4 100644
--- a/src/component/axis/CartesianAxisView.js
+++ b/src/component/axis/CartesianAxisView.js
@@ -2,10 +2,7 @@ import * as zrUtil from 'zrender/src/core/util';
 import * as graphic from '../../util/graphic';
 import AxisBuilder from './AxisBuilder';
 import AxisView from './AxisView';
-import * as cartesianAxisHelper from './cartesianAxisHelper';
-
-var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick;
-var getInterval = AxisBuilder.getInterval;
+import * as cartesianAxisHelper from '../../coord/cartesian/cartesianAxisHelper';
 
 var axisBuilderAttrs = [
     'axisLine', 'axisTickLabel', 'axisName'
@@ -56,7 +53,7 @@ var CartesianAxisView = AxisView.extend({
 
         zrUtil.each(selfBuilderAttrs, function (name) {
             if (axisModel.get(name + '.show')) {
-                this['_' + name](axisModel, gridModel, layout.labelInterval);
+                this['_' + name](axisModel, gridModel);
             }
         }, this);
 
@@ -65,13 +62,16 @@ var CartesianAxisView = AxisView.extend({
         CartesianAxisView.superCall(this, 'render', axisModel, ecModel, api, payload);
     },
 
+    remove: function () {
+        this._splitAreaColors = null;
+    },
+
     /**
      * @param {module:echarts/coord/cartesian/AxisModel} axisModel
      * @param {module:echarts/coord/cartesian/GridModel} gridModel
-     * @param {number|Function} labelInterval
      * @private
      */
-    _splitLine: function (axisModel, gridModel, labelInterval) {
+    _splitLine: function (axisModel, gridModel) {
         var axis = axisModel.axis;
 
         if (axis.scale.isBlank()) {
@@ -82,8 +82,6 @@ var CartesianAxisView = AxisView.extend({
         var lineStyleModel = splitLineModel.getModel('lineStyle');
         var lineColors = lineStyleModel.get('color');
 
-        var lineInterval = getInterval(splitLineModel, labelInterval);
-
         lineColors = zrUtil.isArray(lineColors) ? lineColors : [lineColors];
 
         var gridRect = gridModel.coordinateSystem.getRect();
@@ -91,28 +89,18 @@ var CartesianAxisView = AxisView.extend({
 
         var lineCount = 0;
 
-        var ticksCoords = axis.getTicksCoords(
-            // splitLineModel.get('alignWithLabel')
-        );
-        var ticks = axis.scale.getTicks();
-
-        var showMinLabel = axisModel.get('axisLabel.showMinLabel');
-        var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
+        var ticksCoords = axis.getTicksCoords({
+            tickModel: splitLineModel
+        });
 
         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++) {
-            if (ifIgnoreOnTick(
-                axis, i, lineInterval, ticksCoords.length,
-                showMinLabel, showMaxLabel
-            )) {
-                continue;
-            }
-
-            var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
+            var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord);
 
             if (isHorizontal) {
                 p1[0] = tickCoord;
@@ -129,8 +117,7 @@ var CartesianAxisView = AxisView.extend({
 
             var colorIndex = (lineCount++) % lineColors.length;
             this._axisGroup.add(new graphic.Line(graphic.subPixelOptimizeLine({
-                anid: 'line_' + ticks[i],
-
+                anid: 'line_' + ticksCoords[i].tickValue,
                 shape: {
                     x1: p1[0],
                     y1: p1[1],
@@ -148,10 +135,9 @@ var CartesianAxisView = AxisView.extend({
     /**
      * @param {module:echarts/coord/cartesian/AxisModel} axisModel
      * @param {module:echarts/coord/cartesian/GridModel} gridModel
-     * @param {number|Function} labelInterval
      * @private
      */
-    _splitArea: function (axisModel, gridModel, labelInterval) {
+    _splitArea: function (axisModel, gridModel) {
         var axis = axisModel.axis;
 
         if (axis.scale.isBlank()) {
@@ -164,55 +150,63 @@ var CartesianAxisView = AxisView.extend({
 
         var gridRect = gridModel.coordinateSystem.getRect();
 
-        var ticksCoords = axis.getTicksCoords(
-            // splitAreaModel.get('alignWithLabel')
-        );
-        var ticks = axis.scale.getTicks();
+        var ticksCoords = axis.getTicksCoords({
+            tickModel: splitAreaModel,
+            clamp: true
+        });
 
-        var prevX = axis.toGlobalCoord(ticksCoords[0]);
-        var prevY = axis.toGlobalCoord(ticksCoords[0]);
+        if (!ticksCoords.length) {
+            return;
+        }
 
-        var count = 0;
+        // For Making appropriate splitArea animation, the color and anid
+        // should be corresponding to previous one if possible.
+        var areaColorsLen = areaColors.length;
+        var lastSplitAreaColors = this._splitAreaColors;
+        var newSplitAreaColors = zrUtil.createHashMap();
+        var colorIndex = 0;
+        if (lastSplitAreaColors) {
+            for (var i = 0; i < ticksCoords.length; i++) {
+                var cIndex = lastSplitAreaColors.get(ticksCoords[i].tickValue);
+                if (cIndex != null) {
+                    colorIndex = (cIndex + (areaColorsLen - 1) * i) % areaColorsLen;
+                    break;
+                }
+            }
+        }
 
-        var areaInterval = getInterval(splitAreaModel, labelInterval);
+        var prev = axis.toGlobalCoord(ticksCoords[0].coord);
 
         var areaStyle = areaStyleModel.getAreaStyle();
         areaColors = zrUtil.isArray(areaColors) ? areaColors : [areaColors];
 
-        var showMinLabel = axisModel.get('axisLabel.showMinLabel');
-        var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
-
         for (var i = 1; i < ticksCoords.length; i++) {
-            if (ifIgnoreOnTick(
-                axis, i, areaInterval, ticksCoords.length,
-                showMinLabel, showMaxLabel
-            ) && (i < ticksCoords.length - 1)) {
-                continue;
-            }
-
-            var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
+            var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord);
 
             var x;
             var y;
             var width;
             var height;
             if (axis.isHorizontal()) {
-                x = prevX;
+                x = prev;
                 y = gridRect.y;
                 width = tickCoord - x;
                 height = gridRect.height;
+                prev = x + width;
             }
             else {
                 x = gridRect.x;
-                y = prevY;
+                y = prev;
                 width = gridRect.width;
                 height = tickCoord - y;
+                prev = y + height;
             }
 
-            var colorIndex = (count++) % areaColors.length;
-            this._axisGroup.add(new graphic.Rect({
-                anid: 'area_' + ticks[i],
+            var tickValue = ticksCoords[i - 1].tickValue;
+            newSplitAreaColors.set(tickValue, colorIndex);
 
+            this._axisGroup.add(new graphic.Rect({
+                anid: 'area_' + tickValue,
                 shape: {
                     x: x,
                     y: y,
@@ -225,9 +219,10 @@ var CartesianAxisView = AxisView.extend({
                 silent: true
             }));
 
-            prevX = x + width;
-            prevY = y + height;
+            colorIndex = (colorIndex + 1) % areaColorsLen;
         }
+
+        this._splitAreaColors = newSplitAreaColors;
     }
 });
 
diff --git a/src/component/axis/RadiusAxisView.js b/src/component/axis/RadiusAxisView.js
index 3bc7ef6..11185d0 100644
--- a/src/component/axis/RadiusAxisView.js
+++ b/src/component/axis/RadiusAxisView.js
@@ -60,7 +60,7 @@ export default AxisView.extend({
                 shape: {
                     cx: polar.cx,
                     cy: polar.cy,
-                    r: ticksCoords[i]
+                    r: ticksCoords[i].coord
                 },
                 silent: true
             }));
@@ -83,6 +83,9 @@ export default AxisView.extend({
      * @private
      */
     _splitArea: function (radiusAxisModel, polar, axisAngle, radiusExtent, ticksCoords) {
+        if (!ticksCoords.length) {
+            return;
+        }
 
         var splitAreaModel = radiusAxisModel.getModel('splitArea');
         var areaStyleModel = splitAreaModel.getModel('areaStyle');
@@ -93,7 +96,7 @@ export default AxisView.extend({
 
         var splitAreas = [];
 
-        var prevRadius = ticksCoords[0];
+        var prevRadius = ticksCoords[0].coord;
         for (var i = 1; i < ticksCoords.length; i++) {
             var colorIndex = (lineCount++) % areaColors.length;
             splitAreas[colorIndex] = splitAreas[colorIndex] || [];
@@ -102,13 +105,13 @@ export default AxisView.extend({
                     cx: polar.cx,
                     cy: polar.cy,
                     r0: prevRadius,
-                    r: ticksCoords[i],
+                    r: ticksCoords[i].coord,
                     startAngle: 0,
                     endAngle: Math.PI * 2
                 },
                 silent: true
             }));
-            prevRadius = ticksCoords[i];
+            prevRadius = ticksCoords[i].coord;
         }
 
         // Simple optimization
diff --git a/src/component/axis/SingleAxisView.js b/src/component/axis/SingleAxisView.js
index 16cf20e..3d49fd5 100644
--- a/src/component/axis/SingleAxisView.js
+++ b/src/component/axis/SingleAxisView.js
@@ -2,12 +2,9 @@
 import * as zrUtil from 'zrender/src/core/util';
 import AxisBuilder from './AxisBuilder';
 import * as graphic from '../../util/graphic';
-import * as singleAxisHelper from './singleAxisHelper';
+import * as singleAxisHelper from '../../coord/single/singleAxisHelper';
 import AxisView from './AxisView';
 
-var getInterval = AxisBuilder.getInterval;
-var ifIgnoreOnTick = AxisBuilder.ifIgnoreOnTick;
-
 var axisBuilderAttrs = [
     'axisLine', 'axisTickLabel', 'axisName'
 ];
@@ -35,13 +32,13 @@ var SingleAxisView = AxisView.extend({
         group.add(axisBuilder.getGroup());
 
         if (axisModel.get(selfBuilderAttr + '.show')) {
-            this['_' + selfBuilderAttr](axisModel, layout.labelInterval);
+            this['_' + selfBuilderAttr](axisModel);
         }
 
         SingleAxisView.superCall(this, 'render', axisModel, ecModel, api, payload);
     },
 
-    _splitLine: function(axisModel, labelInterval) {
+    _splitLine: function(axisModel) {
         var axis = axisModel.axis;
 
         if (axis.scale.isBlank()) {
@@ -52,7 +49,6 @@ var SingleAxisView = AxisView.extend({
         var lineStyleModel = splitLineModel.getModel('lineStyle');
         var lineWidth = lineStyleModel.get('width');
         var lineColors = lineStyleModel.get('color');
-        var lineInterval = getInterval(splitLineModel, labelInterval);
 
         lineColors = lineColors instanceof Array ? lineColors : [lineColors];
 
@@ -62,22 +58,15 @@ var SingleAxisView = AxisView.extend({
         var splitLines = [];
         var lineCount = 0;
 
-        var ticksCoords = axis.getTicksCoords();
+        var ticksCoords = axis.getTicksCoords({
+            tickModel: splitLineModel
+        });
 
         var p1 = [];
         var p2 = [];
 
-        var showMinLabel = axisModel.get('axisLabel.showMinLabel');
-        var showMaxLabel = axisModel.get('axisLabel.showMaxLabel');
-
         for (var i = 0; i < ticksCoords.length; ++i) {
-            if (ifIgnoreOnTick(
-                axis, i, lineInterval, ticksCoords.length,
-                showMinLabel, showMaxLabel
-            )) {
-                continue;
-            }
-            var tickCoord = axis.toGlobalCoord(ticksCoords[i]);
+            var tickCoord = axis.toGlobalCoord(ticksCoords[i].coord);
             if (isHorizontal) {
                 p1[0] = tickCoord;
                 p1[1] = gridRect.y;
diff --git a/src/component/axisPointer/CartesianAxisPointer.js b/src/component/axisPointer/CartesianAxisPointer.js
index edaafb9..9963885 100644
--- a/src/component/axisPointer/CartesianAxisPointer.js
+++ b/src/component/axisPointer/CartesianAxisPointer.js
@@ -1,7 +1,7 @@
 import * as graphic from '../../util/graphic';
 import BaseAxisPointer from './BaseAxisPointer';
 import * as viewHelper from './viewHelper';
-import * as cartesianAxisHelper from '../axis/cartesianAxisHelper';
+import * as cartesianAxisHelper from '../../coord/cartesian/cartesianAxisHelper';
 import AxisView from '../axis/AxisView';
 
 var CartesianAxisPointer = BaseAxisPointer.extend({
diff --git a/src/component/axisPointer/SingleAxisPointer.js b/src/component/axisPointer/SingleAxisPointer.js
index 375db96..4f1a9bd 100644
--- a/src/component/axisPointer/SingleAxisPointer.js
+++ b/src/component/axisPointer/SingleAxisPointer.js
@@ -1,7 +1,7 @@
 import * as graphic from '../../util/graphic';
 import BaseAxisPointer from './BaseAxisPointer';
 import * as viewHelper from './viewHelper';
-import * as singleAxisHelper from '../axis/singleAxisHelper';
+import * as singleAxisHelper from '../../coord/single/singleAxisHelper';
 import AxisView from '../axis/AxisView';
 
 var XY = ['x', 'y'];
diff --git a/src/component/radar/RadarView.js b/src/component/radar/RadarView.js
index 4164aaa..49a3457 100644
--- a/src/component/radar/RadarView.js
+++ b/src/component/radar/RadarView.js
@@ -80,7 +80,7 @@ export default echarts.extendComponentView({
                         shape: {
                             cx: cx,
                             cy: cy,
-                            r: ticksRadius[i]
+                            r: ticksRadius[i].coord
                         }
                     }));
                 }
@@ -90,8 +90,8 @@ export default echarts.extendComponentView({
                         shape: {
                             cx: cx,
                             cy: cy,
-                            r0: ticksRadius[i],
-                            r: ticksRadius[i + 1]
+                            r0: ticksRadius[i].coord,
+                            r: ticksRadius[i + 1].coord
                         }
                     }));
                 }
@@ -106,7 +106,7 @@ export default echarts.extendComponentView({
                     ? ticksCoords.length - 1
                     : Math.min(ticksCoords.length - 1, realSplitNumber);
                 return zrUtil.map(ticksCoords, function (tickCoord) {
-                    return radar.coordToPoint(tickCoord, idx);
+                    return radar.coordToPoint(tickCoord.coord, idx);
                 });
             });
 
diff --git a/src/component/timeline/SliderTimelineView.js b/src/component/timeline/SliderTimelineView.js
index c4dd276..1a564b4 100644
--- a/src/component/timeline/SliderTimelineView.js
+++ b/src/component/timeline/SliderTimelineView.js
@@ -287,9 +287,16 @@ export default TimelineView.extend({
         var axisType = timelineModel.get('axisType');
 
         var scale = axisHelper.createScaleByModel(timelineModel, axisType);
+
+        // Customize scale. The `tickValue` is `dataIndex`.
+        scale.getTicks = function () {
+            return data.mapArray(['value'], function (value) {
+                return value;
+            });
+        };
+
         var dataExtent = data.getDataExtent('value');
         scale.setExtent(dataExtent[0], dataExtent[1]);
-        this._customizeScale(scale, data);
         scale.niceTicks();
 
         var axis = new TimelineAxis('value', scale, layoutInfo.axisExtent, axisType);
@@ -298,19 +305,6 @@ export default TimelineView.extend({
         return axis;
     },
 
-    _customizeScale: function (scale, data) {
-
-        scale.getTicks = function () {
-            return data.mapArray(['value'], function (value) {
-                return value;
-            });
-        };
-
-        scale.getTicksLabels = function () {
-            return zrUtil.map(this.getTicks(), scale.getLabel, scale);
-        };
-    },
-
     _createGroup: function (name) {
         var newGroup = this['_' + name] = new graphic.Group();
         this.group.add(newGroup);
@@ -343,23 +337,24 @@ export default TimelineView.extend({
      */
     _renderAxisTick: function (layoutInfo, group, axis, timelineModel) {
         var data = timelineModel.getData();
+        // Show all ticks, despite ignoring strategy.
         var ticks = axis.scale.getTicks();
 
-        each(ticks, function (value, dataIndex) {
-
+        // The value is dataIndex, see the costomized scale.
+        each(ticks, function (value) {
             var tickCoord = axis.dataToCoord(value);
-            var itemModel = data.getItemModel(dataIndex);
+            var itemModel = data.getItemModel(value);
             var itemStyleModel = itemModel.getModel('itemStyle');
             var hoverStyleModel = itemModel.getModel('emphasis.itemStyle');
             var symbolOpt = {
                 position: [tickCoord, 0],
-                onclick: bind(this._changeTimeline, this, dataIndex)
+                onclick: bind(this._changeTimeline, this, value)
             };
             var el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt);
             graphic.setHoverStyle(el, hoverStyleModel.getItemStyle());
 
             if (itemModel.get('tooltip')) {
-                el.dataIndex = dataIndex;
+                el.dataIndex = value;
                 el.dataModel = timelineModel;
             }
             else {
@@ -373,28 +368,23 @@ export default TimelineView.extend({
      * @private
      */
     _renderAxisLabel: function (layoutInfo, group, axis, timelineModel) {
-        var labelModel = timelineModel.getModel('label');
+        var labelModel = axis.getLabelModel();
 
         if (!labelModel.get('show')) {
             return;
         }
 
         var data = timelineModel.getData();
-        var ticks = axis.scale.getTicks();
-        var labels = axisHelper.getFormattedLabels(
-            axis, labelModel.get('formatter')
-        );
-        var labelInterval = axis.getLabelInterval();
+        var labels = axis.getViewLabels();
 
-        each(ticks, function (tick, dataIndex) {
-            if (axis.isLabelIgnored(dataIndex, labelInterval)) {
-                return;
-            }
+        each(labels, function (labelItem) {
+            // The tickValue is dataIndex, see the costomized scale.
+            var dataIndex = labelItem.tickValue;
 
             var itemModel = data.getItemModel(dataIndex);
             var normalLabelModel = itemModel.getModel('label');
             var hoverLabelModel = itemModel.getModel('emphasis.label');
-            var tickCoord = axis.dataToCoord(tick);
+            var tickCoord = axis.dataToCoord(labelItem.tickValue);
             var textEl = new graphic.Text({
                 position: [tickCoord, 0],
                 rotation: layoutInfo.labelRotation - layoutInfo.rotation,
@@ -402,7 +392,7 @@ export default TimelineView.extend({
                 silent: false
             });
             graphic.setTextStyle(textEl.style, normalLabelModel, {
-                text: labels[dataIndex],
+                text: labelItem.formattedLabel,
                 textAlign: layoutInfo.labelAlign,
                 textVerticalAlign: layoutInfo.labelBaseline
             });
diff --git a/src/component/timeline/TimelineAxis.js b/src/component/timeline/TimelineAxis.js
index ea688b6..a398f83 100644
--- a/src/component/timeline/TimelineAxis.js
+++ b/src/component/timeline/TimelineAxis.js
@@ -1,6 +1,5 @@
 import * as zrUtil from 'zrender/src/core/util';
 import Axis from '../../coord/Axis';
-import * as axisHelper from '../../coord/axisHelper';
 
 /**
  * Extend axis 2d
@@ -27,12 +26,6 @@ var TimelineAxis = function (dim, scale, coordExtent, axisType) {
     this.type = axisType || 'value';
 
     /**
-     * @private
-     * @type {number}
-     */
-    this._autoLabelInterval;
-
-    /**
      * Axis model
      * @param {module:echarts/component/TimelineModel}
      */
@@ -44,47 +37,17 @@ TimelineAxis.prototype = {
     constructor: TimelineAxis,
 
     /**
-     * @public
-     * @return {number}
+     * @override
      */
-    getLabelInterval: function () {
-        var timelineModel = this.model;
-        var labelModel = timelineModel.getModel('label');
-        var labelInterval = labelModel.get('interval');
-
-        if (labelInterval != null && labelInterval != 'auto') {
-            return labelInterval;
-        }
-
-        var labelInterval = this._autoLabelInterval;
-
-        if (!labelInterval) {
-            labelInterval = this._autoLabelInterval = axisHelper.getAxisLabelInterval(
-                zrUtil.map(this.scale.getTicks(), this.dataToCoord, this),
-                axisHelper.getFormattedLabels(this, labelModel.get('formatter')),
-                labelModel.getFont(),
-                timelineModel.get('orient') === 'horizontal' ? 0 : 90,
-                labelModel.get('rotate')
-            );
-        }
-
-        return labelInterval;
+    getLabelModel: function () {
+        return this.model.getModel('label');
     },
 
     /**
-     * If label is ignored.
-     * Automatically used when axis is category and label can not be all shown
-     * @public
-     * @param  {number} idx
-     * @return {boolean}
+     * @override
      */
-    isLabelIgnored: function (idx) {
-        if (this.type === 'category') {
-            var labelInterval = this.getLabelInterval();
-            return ((typeof labelInterval === 'function')
-                && !labelInterval(idx, this.scale.getLabel(idx)))
-                || idx % (labelInterval + 1);
-        }
+    isHorizontal: function () {
+        return this.model.get('orient') === 'horizontal';
     }
 
 };
diff --git a/src/coord/Axis.js b/src/coord/Axis.js
index a3f0315..6e6d764 100644
--- a/src/coord/Axis.js
+++ b/src/coord/Axis.js
@@ -1,27 +1,17 @@
-
-import * as zrUtil from 'zrender/src/core/util';
-import * as numberUtil from '../util/number';
+import {each, map, createHashMap} from 'zrender/src/core/util';
+import {linearMap, getPixelPrecision} from '../util/number';
 import * as axisHelper from './axisHelper';
 
-var linearMap = numberUtil.linearMap;
-
-function fixExtentWithBands(extent, nTick) {
-    var size = extent[1] - extent[0];
-    var len = nTick;
-    var margin = size / len / 2;
-    extent[0] += margin;
-    extent[1] -= margin;
-}
+var NORMALIZED_EXTENT = [0, 1];
 
-var normalizedExtent = [0, 1];
 /**
- * @name module:echarts/coord/CartesianAxis
+ * Base class of Axis.
  * @constructor
  */
 var Axis = function (dim, scale, extent) {
 
     /**
-     * Axis dimension. Such as 'x', 'y', 'z', 'angle', 'radius'
+     * Axis dimension. Such as 'x', 'y', 'z', 'angle', 'radius'.
      * @type {string}
      */
     this.dim = dim;
@@ -50,10 +40,12 @@ var Axis = function (dim, scale, extent) {
     this.onBand = false;
 
     /**
+     * Key: tickCategoryInterval
+     * Value: {ticks, labels}
      * @private
-     * @type {number}
+     * @type {HashMap}
      */
-    this._labelInterval;
+    this._ticksCache = createHashMap();
 };
 
 Axis.prototype = {
@@ -95,7 +87,7 @@ Axis.prototype = {
      * @return {number}
      */
     getPixelPrecision: function (dataExtent) {
-        return numberUtil.getPixelPrecision(
+        return getPixelPrecision(
             dataExtent || this.scale.getExtent(),
             this._extent
         );
@@ -128,7 +120,7 @@ Axis.prototype = {
             fixExtentWithBands(extent, scale.count());
         }
 
-        return linearMap(data, normalizedExtent, extent, clamp);
+        return linearMap(data, NORMALIZED_EXTENT, extent, clamp);
     },
 
     /**
@@ -146,7 +138,7 @@ Axis.prototype = {
             fixExtentWithBands(extent, scale.count());
         }
 
-        var t = linearMap(coord, extent, normalizedExtent, clamp);
+        var t = linearMap(coord, extent, NORMALIZED_EXTENT, clamp);
 
         return this.scale.scale(t);
     },
@@ -162,57 +154,58 @@ Axis.prototype = {
     },
 
     /**
-     * @return {Array.<number>}
+     * Different from `zrUtil.map(axis.getTicks(), axis.dataToCoord, axis)`,
+     * `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 {boolean} [opt.clamp] If `false`, clip. If `true`, clamp.
+     * @return {Array.<Object>} [{
+     *     coord: ...,
+     *     tickValue: ...
+     * }, ...]
      */
-    getTicksCoords: function (alignWithLabel) {
-        if (this.onBand && !alignWithLabel) {
-            var bands = this.getBands();
-            var coords = [];
-            for (var i = 0; i < bands.length; i++) {
-                coords.push(bands[i][0]);
-            }
-            if (bands[i - 1]) {
-                coords.push(bands[i - 1][1]);
-            }
-            return coords;
-        }
-        else {
-            return zrUtil.map(this.scale.getTicks(), this.dataToCoord, this);
-        }
+    getTicksCoords: function (opt) {
+        opt = opt || {};
+
+        var tickModel = opt.tickModel || this.model.getModel('axisTick');
+        var alignWithLabel = tickModel.get('alignWithLabel');
+        var tickCategoryInterval = tickModel.get('interval');
+
+        var result = axisHelper.createAxisTicksAndLabels(this, {
+            tickCategoryInterval: tickCategoryInterval
+        });
+        var ticks = result.ticks;
+
+        var ticksCoords = map(ticks, function (tickValue) {
+            return {
+                coord: this.dataToCoord(tickValue),
+                tickValue: tickValue
+            };
+        }, this);
+
+        fixOnBandTicksCoords(
+            this, ticksCoords, result.tickCategoryInterval, alignWithLabel, opt.clamp
+        );
+
+        return ticksCoords;
     },
 
     /**
-     * Coords of labels are on the ticks or on the middle of bands
-     * @return {Array.<number>}
+     * @return {Array.<Object>} [{
+     *     formattedLabel: string,
+     *     rawLabel: axis.scale.getLabel(tickValue)
+     *     tickValue: number
+     * }, ...]
      */
-    getLabelsCoords: function () {
-        return zrUtil.map(this.scale.getTicks(), this.dataToCoord, this);
+    getViewLabels: function () {
+        return axisHelper.createAxisTicksAndLabels(this, {
+            tickCategoryInterval: this.model.get('axisTick.interval')
+        }).labels;
     },
 
-    /**
-     * Get bands.
-     *
-     * If axis has labels [1, 2, 3, 4]. Bands on the axis are
-     * |---1---|---2---|---3---|---4---|.
-     *
-     * @return {Array}
-     */
-        // FIXME Situation when labels is on ticks
-    getBands: function () {
-        var extent = this.getExtent();
-        var bands = [];
-        var len = this.scale.count();
-        var start = extent[0];
-        var end = extent[1];
-        var span = end - start;
-
-        for (var i = 0; i < len; i++) {
-            bands.push([
-                span * i / len + start,
-                span * (i + 1) / len + start
-            ]);
-        }
-        return bands;
+    getLabelModel: function () {
+        return this.model.getModel('axisLabel');
     },
 
     /**
@@ -242,42 +235,64 @@ Axis.prototype = {
      * @abstract
      * @return {number} Get axis rotate, by degree.
      */
-    getRotate: null,
+    getRotate: null
 
-    /**
-     * Get interval of the axis label.
-     * To get precise result, at least one of `getRotate` and `isHorizontal`
-     * should be implemented.
-     * @return {number}
-     */
-    getLabelInterval: function () {
-        var labelInterval = this._labelInterval;
-        if (!labelInterval) {
-            var axisModel = this.model;
-            var labelModel = axisModel.getModel('axisLabel');
-            labelInterval = labelModel.get('interval');
-
-            if (this.type === 'category'
-                && (labelInterval == null || labelInterval === 'auto')
-            ) {
-                labelInterval = axisHelper.getAxisLabelInterval(
-                    zrUtil.map(this.scale.getTicks(), this.dataToCoord, this),
-                    axisModel.getFormattedLabels(),
-                    labelModel.getFont(),
-                    this.getRotate
-                        ? this.getRotate()
-                        : (this.isHorizontal && !this.isHorizontal())
-                        ? 90
-                        : 0,
-                    labelModel.get('rotate')
-                );
-            }
-
-            this._labelInterval = labelInterval;
+};
+
+function fixExtentWithBands(extent, nTick) {
+    var size = extent[1] - extent[0];
+    var len = nTick;
+    var margin = size / len / 2;
+    extent[0] += margin;
+    extent[1] -= margin;
+}
+
+// If axis has labels [1, 2, 3, 4]. Bands on the axis are
+// |---1---|---2---|---3---|---4---|.
+// So the displayed ticks and splitLine/splitArea should between
+// each data item, otherwise cause misleading (e.g., split tow bars
+// of a single data item when there are two bar series).
+// Also consider if tickCategoryInterval > 0 and onBand, ticks and
+// splitLine/spliteArea should layout appropriately corresponding
+// to displayed labels. (So we should not use `getBandWidth` in this
+// case).
+function fixOnBandTicksCoords(axis, ticksCoords, tickCategoryInterval, alignWithLabel, clamp) {
+    var ticksLen = ticksCoords.length;
+    if (axis.onBand && !alignWithLabel && ticksLen) {
+        var axisExtent = axis.getExtent();
+        var last;
+        if (ticksLen === 1) {
+            ticksCoords[0].coord = axisExtent[0];
+            last = ticksCoords[1] = {coord: axisExtent[0]};
+        }
+        else {
+            var shift = (ticksCoords[1].coord - ticksCoords[0].coord);
+            each(ticksCoords, function (ticksItem) {
+                ticksItem.coord -= shift / 2;
+                var tickCategoryInterval = tickCategoryInterval || 0;
+                // Avoid split a single data item when odd interval.
+                if (tickCategoryInterval % 2 > 0) {
+                    ticksItem.coord -= shift / ((tickCategoryInterval + 1) * 2);
+                }
+            });
+            last = {coord: ticksCoords[ticksLen - 1].coord + shift};
+            ticksCoords.push(last);
         }
-        return labelInterval;
-    }
 
-};
+        var inverse = axisExtent[0] > axisExtent[1];
+        if (inverse
+            ? ticksCoords[0].coord > axisExtent[0]
+            : ticksCoords[0].coord < axisExtent[0]
+        ) {
+            clamp ? (ticksCoords[0].coord = axisExtent[0]) : ticksCoords.shift();
+        }
+        if (inverse
+            ? last.coord < axisExtent[1]
+            : last.coord > axisExtent[1]
+        ) {
+            clamp ? (last.coord = axisExtent[1]) : ticksCoords.pop();
+        }
+    }
+}
 
-export default Axis;
\ No newline at end of file
+export default Axis;
diff --git a/src/coord/axisHelper.js b/src/coord/axisHelper.js
index ec58e5e..05cab1b 100644
--- a/src/coord/axisHelper.js
+++ b/src/coord/axisHelper.js
@@ -6,10 +6,14 @@ import IntervalScale from '../scale/Interval';
 import Scale from '../scale/Scale';
 import * as numberUtil from '../util/number';
 import {calBarWidthAndOffset} from '../layout/barGrid';
+import {makeInner} from '../util/model';
+import BoundingRect from 'zrender/src/core/BoundingRect';
 
 import '../scale/Time';
 import '../scale/Log';
 
+var inner = makeInner();
+
 /**
  * Get axis scale extent before niced.
  * Item of returned array can only be number (including Infinity and NaN).
@@ -253,94 +257,413 @@ export function ifAxisCrossZero(axis) {
 }
 
 /**
- * @param {Array.<number>} tickCoords In axis self coordinate.
- * @param {Array.<string>} labels
- * @param {string} font
- * @param {number} axisRotate 0: towards right horizontally, clock-wise is negative.
- * @param {number} [labelRotate=0] 0: towards right horizontally, clock-wise is negative.
+ * @param {module:echarts/coord/Axis} axis
+ * @return {Function} Label formatter function.
+ *         param: {number} tickValue,
+ *         param: {number} idx, the index in all ticks.
+ *                         If category axis, this param is not requied.
+ *         return: {string} label string.
+ */
+function makeLabelFormatter(axis) {
+    var labelFormatter = axis.getLabelModel().get('formatter');
+    var categoryTickStart = axis.type === 'category' ? axis.scale.getExtent()[0] : null;
+
+    if (typeof labelFormatter === 'string') {
+        labelFormatter = (function (tpl) {
+            return function (val) {
+                return tpl.replace('{value}', val != null ? val : '');
+            };
+        })(labelFormatter);
+        // Consider empty array
+        return labelFormatter;
+    }
+    else if (typeof labelFormatter === 'function') {
+        return function (tickValue, idx) {
+            // The original intention of `idx` is "the index of the tick in all ticks".
+            // But the previous implementation of category axis do not consider the
+            // `axisLabel.interval`, which cause that, for example, the `interval` is
+            // `1`, then the ticks "name5", "name7", "name9" are displayed, where the
+            // corresponding `idx` are `0`, `2`, `4`, but not `0`, `1`, `2`. So we keep
+            // the definition here for back compatibility.
+            if (categoryTickStart != null) {
+                idx = tickValue - categoryTickStart;
+            }
+            return labelFormatter(getAxisRawValue(axis, tickValue), idx);
+        };
+    }
+    else {
+        return function (tick) {
+            return axis.scale.getLabel(tick);
+        };
+    }
+}
+
+export function getAxisRawValue(axis, value) {
+    // In category axis with data zoom, tick is not the original
+    // index of axis.data. So tick should not be exposed to user
+    // in category axis.
+    return axis.type === 'category' ? axis.scale.getLabel(value) : value;
+}
+
+/**
+ * Performance sensible in the large category data case.
+ * @param {module:echats/coord/Axis} axis
+ * @param {Object} opt
+ * @param {number} [opt.tickCategoryInterval] Can be null/'auto'. Can also be
+ *        axisTick.interval, splitLine.interval, splitArea.interval.
+ * @return {Object} {
+ *     ticks: [number, ...]
+ *     labels: [{
+ *         formattedLabel: string,
+ *         rawLabel: string,
+ *         tickValue: number
+ *     }, ...],
+ *     labelCategoryInterval,
+ *     tickCategoryInterval
+ * }
+ */
+export function createAxisTicksAndLabels(axis, opt) {
+    // Only ordinal scale support tick interval
+    return axis.type === 'category'
+        ? createCategoryTicksAndLabels(axis, opt)
+        : createRealNumberTicksAndLabels(axis, opt);
+}
+
+function createCategoryTicksAndLabels(axis, opt) {
+    var labelModel = axis.getLabelModel();
+
+    // (1) Only add min max label here but leave overlap checking
+    // to render stage, which also ensure the returned list
+    // suitable for splitLine and splitArea rendering.
+    // (2) Scales except category always contain min max label so
+    // do not need to perform this process.
+    var showMinMax = {
+        min: labelModel.get('showMinLabel'),
+        max: labelModel.get('showMaxLabel')
+    };
+
+    // Large category data calculation is performence sensitive, and ticks and label
+    // probably be fetched by multiple times. So we cache the result.
+    // axis is created each time during a ec process, so we do not need to clear cache.
+    var ticksCache = getListCache(axis, 'ticks');
+    var labelsCache = getListCache(axis, 'labels');
+
+    var labelCategoryInterval = normalizeAuto(labelModel.get('interval'));
+    var labels = listCacheGet(labelsCache, labelCategoryInterval);
+    if (!labels) {
+        if (zrUtil.isFunction(labelCategoryInterval)) {
+            labels = makeLabelsByCustomizedCategoryInterval(axis, labelCategoryInterval);
+        }
+        else {
+            if (labelCategoryInterval === 'auto') {
+                labelCategoryInterval = calculateAutoCategoryInterval(axis);
+            }
+            labels = makeLabelsByNumericCategoryInterval(axis, labelCategoryInterval, showMinMax);
+        }
+        listCacheSet(labelsCache, labelCategoryInterval, labels);
+    }
+
+    var tickCategoryInterval = normalizeAuto(opt.tickCategoryInterval);
+    var ticks = listCacheGet(ticksCache, tickCategoryInterval);
+    if (!ticks) {
+        if (zrUtil.isFunction(tickCategoryInterval)) {
+            ticks = makeLabelsByCustomizedCategoryInterval(axis, tickCategoryInterval, true);
+        }
+        else if (tickCategoryInterval === 'auto' || tickCategoryInterval === labelCategoryInterval) {
+            // Always use label interval by default.
+            tickCategoryInterval = labelCategoryInterval;
+            ticks = zrUtil.map(labels, function (labelItem) {
+                return labelItem.tickValue;
+            });
+        }
+        else {
+            ticks = makeLabelsByNumericCategoryInterval(axis, tickCategoryInterval, showMinMax, true);
+        }
+        listCacheSet(ticksCache, tickCategoryInterval, ticks);
+    }
+
+    return {
+        ticks: ticks,
+        labels: labels,
+        labelCategoryInterval: labelCategoryInterval,
+        tickCategoryInterval: tickCategoryInterval
+    };
+}
+
+function createRealNumberTicksAndLabels(axis, opt) {
+    var ticks = axis.scale.getTicks();
+    var labelFormatter = makeLabelFormatter(axis);
+    var labels = zrUtil.map(ticks, function (tickValue, idx) {
+        return {
+            formattedLabel: labelFormatter(tickValue, idx),
+            rawLabel: axis.scale.getLabel(tickValue),
+            tickValue: tickValue
+        };
+    });
+
+    return {ticks: ticks, labels: labels};
+}
+
+function getListCache(axis, prop) {
+    // Because key can be funciton, and cache size always be small, we use array cache.
+    return inner(axis)[prop] || (inner(axis)[prop] = []);
+}
+
+function listCacheGet(cache, key) {
+    for (var i = 0; i < cache.length; i++) {
+        if (cache[i].key === key) {
+            return cache[i].value;
+        }
+    }
+}
+
+function listCacheSet(cache, key, value) {
+    cache.push({key: key, value: value});
+}
+
+/**
+ * Performance sensitive for large category data.
+ * Get interval of the axis label.
+ * To get precise result, at least one of `getRotate` and `isHorizontal`
+ * should be implemented.
+ * @param {module:echarts/coord/Axis} axis
  * @return {number}
  */
-export function getAxisLabelInterval(tickCoords, labels, font, axisRotate, labelRotate) {
-    var textSpaceTakenRect;
-    var autoLabelInterval = 0;
-    var accumulatedLabelInterval = 0;
-    var rotation = (axisRotate - labelRotate) / 180 * Math.PI;
+function calculateAutoCategoryInterval(axis) {
+    var params = fetchAutoCategoryIntervalCalculationParams(axis);
+    var labelFormatter = makeLabelFormatter(axis);
+    var rotation = (params.axisRotate - params.labelRotate) / 180 * Math.PI;
+
+    var ordinalScale = axis.scale;
+    var ordinalExtent = ordinalScale.getExtent();
+    // Providing this method is for optimization:
+    // avoid generating a long array by `getTicks`
+    // in large category data case.
+    var tickCount = ordinalScale.count();
 
     var step = 1;
-    if (labels.length > 40) {
-        // Simple optimization for large amount of labels
-        step = Math.floor(labels.length / 40);
+    // Simple optimization. Empirical value: tick count less 40.
+    if (tickCount > 40) {
+        step = Math.max(1, Math.floor(tickCount / 40));
     }
 
-    for (var i = 0; i < tickCoords.length; i += step) {
-        var tickCoord = tickCoords[i];
+    var textSpaceTakenRect;
+    var accumulatedLabelInterval = 0;
 
+    for (var tickValue = ordinalExtent[0]; tickValue <= ordinalExtent[1]; ) {
+        var formattedLabel = labelFormatter(tickValue);
         // Not precise, do not consider align and vertical align
         // and each distance from axis line yet.
         var rect = textContain.getBoundingRect(
-            labels[i], font, 'center', 'top'
+            formattedLabel, params.font, 'center', 'top'
         );
+        // Polar is also calculated in assumptive linear layout here.
+        var tickCoord = axis.dataToCoord(tickValue);
         rect.x += tickCoord * Math.cos(rotation);
         rect.y += tickCoord * Math.sin(rotation);
 
         // Magic number
         rect.width *= 1.3;
         rect.height *= 1.3;
-
         if (!textSpaceTakenRect) {
-            textSpaceTakenRect = rect.clone();
+            textSpaceTakenRect = rect;
+            tickValue += step;
         }
-        // There is no space for current label;
+        // There is no space for current label.
         else if (textSpaceTakenRect.intersect(rect)) {
             accumulatedLabelInterval++;
-            autoLabelInterval = Math.max(autoLabelInterval, accumulatedLabelInterval);
+            tickValue++;
         }
         else {
             textSpaceTakenRect.union(rect);
-            // Reset
-            accumulatedLabelInterval = 0;
+            if (accumulatedLabelInterval) {
+                // Optimize: add step to escape uncessary loop.
+                step += accumulatedLabelInterval;
+                accumulatedLabelInterval = 0;
+            }
+            tickValue += step;
         }
     }
-    if (autoLabelInterval === 0 && step > 1) {
-        return step;
+
+    if (accumulatedLabelInterval) {
+        step += accumulatedLabelInterval;
     }
-    return (autoLabelInterval + 1) * step - 1;
+
+    var cache = inner(axis.model);
+    var lastStep = cache.lastStep;
+    var lastTickCount = cache.lastTickCount;
+
+    // Use cache to keep interval stable while moving zoom window,
+    // otherwise the calculated interval might jitter when the zoom
+    // window size is close to the interval-changing size.
+    if (lastStep != null
+        && lastTickCount != null
+        && Math.abs(lastStep - step) <= 1
+        && Math.abs(lastTickCount - tickCount) <= 1
+        // Always choose the bigger one, otherwise the critical
+        // point is not the same when zooming in or zooming out.
+        && lastStep > step
+    ) {
+        step = lastStep;
+    }
+    // Only update cache if cache not used, otherwise the
+    // changing of interval is too insensitive.
+    else {
+        cache.lastTickCount = tickCount;
+        cache.lastStep = step;
+    }
+
+    return step - 1;
+}
+
+function fetchAutoCategoryIntervalCalculationParams(axis) {
+    var labelModel = axis.getLabelModel();
+    return {
+        axisRotate: axis.getRotate
+            ? axis.getRotate()
+            : (axis.isHorizontal && !axis.isHorizontal())
+            ? 90
+            : 0,
+        labelRotate: labelModel.get('rotate') || 0,
+        font: labelModel.getFont()
+    };
+}
+
+function makeLabelsByNumericCategoryInterval(axis, categoryInterval, showMinMax, onlyTick) {
+    var labelFormatter = makeLabelFormatter(axis);
+    var ordinalScale = axis.scale;
+    var ordinalExtent = ordinalScale.getExtent();
+    var result = [];
+
+    // TODO: axisType: ordinalTime, pick the tick from each month/day/year/...
+
+    var step = Math.max((categoryInterval || 0) + 1, 1);
+    var startTick = ordinalExtent[0];
+    var tickCount = ordinalScale.count();
+
+    // Calculate start tick based on zero if possible to keep label consistent
+    // while zooming and moving while interval > 0. Otherwise the selection
+    // of displayable ticks and symbols probably keep changing.
+    // 3 is empirical value.
+    if (startTick !== 0 && step > 1 && tickCount / step > 2) {
+        startTick = Math.round(Math.ceil(startTick / step) * step);
+    }
+
+    if (showMinMax.min && startTick !== ordinalExtent[0]) {
+        addItem(ordinalExtent[0]);
+    }
+
+    // Optimize: avoid generating large array by `ordinalScale.getTicks()`.
+    var tickValue = startTick;
+    for (; tickValue <= ordinalExtent[1]; tickValue += step) {
+        addItem(tickValue);
+    }
+
+    if (showMinMax.max && tickValue !== ordinalExtent[1]) {
+        addItem(ordinalExtent[1]);
+    }
+
+    function addItem(tVal) {
+        result.push(onlyTick
+            ? tVal
+            : {
+                formattedLabel: labelFormatter(tVal),
+                rawLabel: ordinalScale.getLabel(tVal),
+                tickValue: tVal
+            }
+        );
+    }
+
+    return result;
+}
+
+// When interval is function, the result `false` means ignore the tick.
+// It is time consuming for large category data.
+function makeLabelsByCustomizedCategoryInterval(axis, categoryInterval, onlyTick) {
+    var ordinalScale = axis.scale;
+    var labelFormatter = makeLabelFormatter(axis);
+    var result = [];
+
+    zrUtil.each(ordinalScale.getTicks(), function (tickValue) {
+        var rawLabel = ordinalScale.getLabel(tickValue);
+        if (categoryInterval(tickValue, rawLabel)) {
+            result.push(onlyTick
+                ? tickValue
+                : {
+                    formattedLabel: labelFormatter(tickValue),
+                    rawLabel: rawLabel,
+                    tickValue: tickValue
+                }
+            );
+        }
+    });
+
+    return result;
+}
+
+// Can be null|'auto'
+function normalizeAuto(setting) {
+    return setting == null ? 'auto' : setting;
 }
 
 /**
- * @param {Object} axis
- * @param {Function} labelFormatter
- * @return {Array.<string>}
+ * @param {module:echarts/coord/Axis} axis
+ * @return {module:zrender/core/BoundingRect} Be null/undefined if no labels.
  */
-export function getFormattedLabels(axis, labelFormatter) {
-    var scale = axis.scale;
-    var labels = scale.getTicksLabels();
-    var ticks = scale.getTicks();
-    if (typeof labelFormatter === 'string') {
-        labelFormatter = (function (tpl) {
-            return function (val) {
-                return tpl.replace('{value}', val != null ? val : '');
-            };
-        })(labelFormatter);
-        // Consider empty array
-        return zrUtil.map(labels, labelFormatter);
+export function estimateLabelUnionRect(axis) {
+    var axisModel = axis.model;
+
+    if (!axisModel.get('axisLabel.show')) {
+        return;
     }
-    else if (typeof labelFormatter === 'function') {
-        return zrUtil.map(ticks, function (tick, idx) {
-            return labelFormatter(
-                getAxisRawValue(axis, tick),
-                idx
-            );
-        }, this);
+
+    var scale = axis.scale;
+    var isCategory = axis.type === 'category';
+
+    var realNumberScaleTicks;
+    var tickCount;
+    var categoryScaleExtent = scale.getExtent();
+
+    // Optimize for large category data, avoid call `getTicks()`.
+    if (isCategory) {
+        tickCount = scale.count();
     }
     else {
-        return labels;
+        realNumberScaleTicks = scale.getTicks();
+        tickCount = realNumberScaleTicks.length;
     }
+
+    var axisLabelModel = axis.getLabelModel();
+    var labelFormatter = makeLabelFormatter(axis);
+
+    var rect;
+    var step = 1;
+    // Simple optimization for large amount of labels
+    if (tickCount > 40) {
+        step = Math.ceil(tickCount / 40);
+    }
+    for (var i = 0; i < tickCount; i += step) {
+        var tickValue = realNumberScaleTicks ? realNumberScaleTicks[i] : categoryScaleExtent[0] + i;
+        var label = labelFormatter(tickValue);
+        var unrotatedSingleRect = axisLabelModel.getTextRect(label);
+        var singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);
+
+        rect ? rect.union(singleRect) : (rect = singleRect);
+    }
+
+    return rect;
 }
 
-export function getAxisRawValue(axis, value) {
-    // In category axis with data zoom, tick is not the original
-    // index of axis.data. So tick should not be exposed to user
-    // in category axis.
-    return axis.type === 'category' ? axis.scale.getLabel(value) : value;
+function rotateTextRect(textRect, rotate) {
+    var rotateRadians = rotate * Math.PI / 180;
+    var boundingBox = textRect.plain();
+    var beforeWidth = boundingBox.width;
+    var beforeHeight = boundingBox.height;
+    var afterWidth = beforeWidth * Math.cos(rotateRadians) + beforeHeight * Math.sin(rotateRadians);
+    var afterHeight = beforeWidth * Math.sin(rotateRadians) + beforeHeight * Math.cos(rotateRadians);
+    var rotatedRect = new BoundingRect(boundingBox.x, boundingBox.y, afterWidth, afterHeight);
+
+    return rotatedRect;
 }
+
+
diff --git a/src/coord/axisModelCommonMixin.js b/src/coord/axisModelCommonMixin.js
index aaa9d85..b84d848 100644
--- a/src/coord/axisModelCommonMixin.js
+++ b/src/coord/axisModelCommonMixin.js
@@ -4,17 +4,6 @@ import * as axisHelper from './axisHelper';
 export default {
 
     /**
-     * Format labels
-     * @return {Array.<string>}
-     */
-    getFormattedLabels: function () {
-        return axisHelper.getFormattedLabels(
-            this.axis,
-            this.get('axisLabel.formatter')
-        );
-    },
-
-    /**
      * @param {boolean} origin
      * @return {number|string} min value or 'dataMin' or null/undefined (means auto) or NaN
      */
diff --git a/src/coord/cartesian/Axis2D.js b/src/coord/cartesian/Axis2D.js
index 5de8dc6..a5042af 100644
--- a/src/coord/cartesian/Axis2D.js
+++ b/src/coord/cartesian/Axis2D.js
@@ -82,21 +82,6 @@ Axis2D.prototype = {
     },
 
     /**
-     * If label is ignored.
-     * Automatically used when axis is category and label can not be all shown
-     * @param  {number}  idx
-     * @return {boolean}
-     */
-    isLabelIgnored: function (idx) {
-        if (this.type === 'category') {
-            var labelInterval = this.getLabelInterval();
-            return ((typeof labelInterval === 'function')
-                && !labelInterval(idx, this.scale.getLabel(idx)))
-                || idx % (labelInterval + 1);
-        }
-    },
-
-    /**
      * @override
      */
     pointToData: function (point, clamp) {
diff --git a/src/coord/cartesian/Grid.js b/src/coord/cartesian/Grid.js
index 39a53e6..fa81b4d 100644
--- a/src/coord/cartesian/Grid.js
+++ b/src/coord/cartesian/Grid.js
@@ -5,10 +5,14 @@
  */
 
 import {__DEV__} from '../../config';
-import * as zrUtil from 'zrender/src/core/util';
-import BoundingRect from 'zrender/src/core/BoundingRect';
+import {isObject, each, map, indexOf, retrieve} from 'zrender/src/core/util';
 import {getLayoutRect} from '../../util/layout';
-import * as axisHelper from '../../coord/axisHelper';
+import {
+    createScaleByModel,
+    ifAxisCrossZero,
+    niceScaleExtent,
+    estimateLabelUnionRect
+} from '../../coord/axisHelper';
 import Cartesian2D from './Cartesian2D';
 import Axis2D from './Axis2D';
 import CoordinateSystem from '../../CoordinateSystem';
@@ -16,10 +20,6 @@ import CoordinateSystem from '../../CoordinateSystem';
 // Depends on GridModel, AxisModel, which performs preprocess.
 import './GridModel';
 
-var each = zrUtil.each;
-var ifAxisCrossZero = axisHelper.ifAxisCrossZero;
-var niceScaleExtent = axisHelper.niceScaleExtent;
-
 /**
  * Check if the axis is used in the specified grid
  * @inner
@@ -28,40 +28,6 @@ function isAxisUsedInTheGrid(axisModel, gridModel, ecModel) {
     return axisModel.getCoordSysModel() === gridModel;
 }
 
-function rotateTextRect(textRect, rotate) {
-  var rotateRadians = rotate * Math.PI / 180;
-  var boundingBox = textRect.plain();
-  var beforeWidth = boundingBox.width;
-  var beforeHeight = boundingBox.height;
-  var afterWidth = beforeWidth * Math.cos(rotateRadians) + beforeHeight * Math.sin(rotateRadians);
-  var afterHeight = beforeWidth * Math.sin(rotateRadians) + beforeHeight * Math.cos(rotateRadians);
-  var rotatedRect = new BoundingRect(boundingBox.x, boundingBox.y, afterWidth, afterHeight);
-
-  return rotatedRect;
-}
-
-function getLabelUnionRect(axis) {
-    var axisModel = axis.model;
-    var labels = axisModel.get('axisLabel.show') ? axisModel.getFormattedLabels() : [];
-    var axisLabelModel = axisModel.getModel('axisLabel');
-    var rect;
-    var step = 1;
-    var labelCount = labels.length;
-    if (labelCount > 40) {
-        // Simple optimization for large amount of labels
-        step = Math.ceil(labelCount / 40);
-    }
-    for (var i = 0; i < labelCount; i += step) {
-        if (!axis.isLabelIgnored(i)) {
-            var unrotatedSingleRect = axisLabelModel.getTextRect(labels[i]);
-            var singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);
-
-            rect ? rect.union(singleRect) : (rect = singleRect);
-        }
-    }
-    return rect;
-}
-
 function Grid(gridModel, ecModel, api) {
     /**
      * @type {Object.<string, module:echarts/coord/cartesian/Cartesian2D>}
@@ -191,7 +157,7 @@ gridProto.resize = function (gridModel, api, ignoreContainLabel) {
     if (!ignoreContainLabel && gridModel.get('containLabel')) {
         each(axesList, function (axis) {
             if (!axis.model.get('axisLabel.inside')) {
-                var labelUnionRect = getLabelUnionRect(axis);
+                var labelUnionRect = estimateLabelUnionRect(axis);
                 if (labelUnionRect) {
                     var dim = axis.isHorizontal() ? 'height' : 'width';
                     var margin = axis.model.get('axisLabel.margin');
@@ -262,7 +228,7 @@ gridProto.getCartesian = function (xAxisIndex, yAxisIndex) {
         return this._coordsMap[key];
     }
 
-    if (zrUtil.isObject(xAxisIndex)) {
+    if (isObject(xAxisIndex)) {
         yAxisIndex = xAxisIndex.yAxisIndex;
         xAxisIndex = xAxisIndex.xAxisIndex;
     }
@@ -324,7 +290,7 @@ gridProto._findConvertTarget = function (ecModel, finder) {
 
     if (seriesModel) {
         cartesian = seriesModel.coordinateSystem;
-        zrUtil.indexOf(coordsList, cartesian) < 0 && (cartesian = null);
+        indexOf(coordsList, cartesian) < 0 && (cartesian = null);
     }
     else if (xAxisModel && yAxisModel) {
         cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
@@ -438,7 +404,7 @@ gridProto._initCartesian = function (gridModel, ecModel, api) {
             axisPositionUsed[axisPosition] = true;
 
             var axis = new Axis2D(
-                axisType, axisHelper.createScaleByModel(axisModel),
+                axisType, createScaleByModel(axisModel),
                 [0, 0],
                 axisModel.get('type'),
                 axisPosition
@@ -475,7 +441,7 @@ gridProto._initCartesian = function (gridModel, ecModel, api) {
  */
 gridProto._updateScale = function (ecModel, gridModel) {
     // Reset scale
-    zrUtil.each(this._axesList, function (axis) {
+    each(this._axesList, function (axis) {
         axis.scale.setExtent(Infinity, -Infinity);
     });
     ecModel.eachSeries(function (seriesModel) {
@@ -523,8 +489,8 @@ gridProto.getTooltipAxes = function (dim) {
         var baseAxis = (dim != null && dim !== 'auto')
             ? cartesian.getAxis(dim) : cartesian.getBaseAxis();
         var otherAxis = cartesian.getOtherAxis(baseAxis);
-        zrUtil.indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
-        zrUtil.indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
+        indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
+        indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
     });
 
     return {baseAxes: baseAxes, otherAxes: otherAxes};
@@ -559,12 +525,12 @@ var axesTypes = ['xAxis', 'yAxis'];
  * @inner
  */
 function findAxesModels(seriesModel, ecModel) {
-    return zrUtil.map(axesTypes, function (axisType) {
+    return map(axesTypes, function (axisType) {
         var axisModel = seriesModel.getReferringComponents(axisType)[0];
 
         if (__DEV__) {
             if (!axisModel) {
-                throw new Error(axisType + ' "' + zrUtil.retrieve(
+                throw new Error(axisType + ' "' + retrieve(
                     seriesModel.get(axisType + 'Index'),
                     seriesModel.get(axisType + 'Id'),
                     0
@@ -611,7 +577,7 @@ Grid.create = function (ecModel, api) {
         if (__DEV__) {
             if (!gridModel) {
                 throw new Error(
-                    'Grid "' + zrUtil.retrieve(
+                    'Grid "' + retrieve(
                         xAxisModel.get('gridIndex'),
                         xAxisModel.get('gridId'),
                         0
diff --git a/src/component/axis/cartesianAxisHelper.js b/src/coord/cartesian/cartesianAxisHelper.js
similarity index 93%
rename from src/component/axis/cartesianAxisHelper.js
rename to src/coord/cartesian/cartesianAxisHelper.js
index 461c871..0ba71d9 100644
--- a/src/component/axis/cartesianAxisHelper.js
+++ b/src/coord/cartesian/cartesianAxisHelper.js
@@ -1,10 +1,13 @@
 import * as zrUtil from 'zrender/src/core/util';
 
 /**
+ * Can only be called after coordinate system creation stage.
+ * (Can be called before coordinate system update stage).
+ *
  * @param {Object} opt {labelInside}
  * @return {Object} {
  *  position, rotation, labelDirection, labelOffset,
- *  tickDirection, labelRotate, labelInterval, z2
+ *  tickDirection, labelRotate, z2
  * }
  */
 export function layout(gridModel, axisModel, opt) {
@@ -58,9 +61,6 @@ export function layout(gridModel, axisModel, opt) {
     var labelRotate = axisModel.get('axisLabel.rotate');
     layout.labelRotate = axisPosition === 'top' ? -labelRotate : labelRotate;
 
-    // label interval when auto mode.
-    layout.labelInterval = axis.getLabelInterval();
-
     // Over splitLine and splitArea
     layout.z2 = 1;
 
diff --git a/src/coord/parallel/Parallel.js b/src/coord/parallel/Parallel.js
index d5dbae5..ee2781b 100644
--- a/src/coord/parallel/Parallel.js
+++ b/src/coord/parallel/Parallel.js
@@ -302,8 +302,7 @@ Parallel.prototype = {
                 axisLabelShow: posInfo.axisLabelShow,
                 nameTruncateMaxWidth: posInfo.nameTruncateMaxWidth,
                 tickDirection: 1,
-                labelDirection: 1,
-                labelInterval: axes.get(dim).getLabelInterval()
+                labelDirection: 1
             };
         }, this);
     },
diff --git a/src/coord/single/SingleAxis.js b/src/coord/single/SingleAxis.js
index 5017b9c..65de49e 100644
--- a/src/coord/single/SingleAxis.js
+++ b/src/coord/single/SingleAxis.js
@@ -42,11 +42,6 @@ var SingleAxis = function (dim, scale, coordExtent, axisType, position) {
      */
     this.orient = null;
 
-    /**
-     * @type {number}
-     */
-    this._labelInterval = null;
-
 };
 
 SingleAxis.prototype = {
diff --git a/src/component/axis/singleAxisHelper.js b/src/coord/single/singleAxisHelper.js
similarity index 91%
rename from src/component/axis/singleAxisHelper.js
rename to src/coord/single/singleAxisHelper.js
index 278f36f..77cdbe0 100644
--- a/src/component/axis/singleAxisHelper.js
+++ b/src/coord/single/singleAxisHelper.js
@@ -4,10 +4,10 @@ import * as zrUtil from 'zrender/src/core/util';
  * @param {Object} opt {labelInside}
  * @return {Object} {
  *  position, rotation, labelDirection, labelOffset,
- *  tickDirection, labelRotate, labelInterval, z2
+ *  tickDirection, labelRotate, z2
  * }
  */
-export function layout (axisModel, opt) {
+export function layout(axisModel, opt) {
     opt = opt || {};
     var single = axisModel.coordinateSystem;
     var axis = axisModel.axis;
@@ -54,8 +54,6 @@ export function layout (axisModel, opt) {
     labelRotation == null && (labelRotation = axisModel.get('axisLabel.rotate'));
     layout.labelRotation = axisPosition === 'top' ? -labelRotation : labelRotation;
 
-    layout.labelInterval = axis.getLabelInterval();
-
     layout.z2 = 1;
 
     return layout;
diff --git a/src/layout/points.js b/src/layout/points.js
index eddfe11..94e5e17 100644
--- a/src/layout/points.js
+++ b/src/layout/points.js
@@ -23,11 +23,12 @@ export default function (seriesType) {
             }).slice(0, 2);
             var dimLen = dims.length;
 
+            var stackResultDim = data.getCalculationInfo('stackResultDimension');
             if (isDimensionStacked(data, dims[0], dims[1])) {
-                dims[0] = data.getCalculationInfo('stackResultDimension');
+                dims[0] = stackResultDim;
             }
             if (isDimensionStacked(data, dims[1], dims[0])) {
-                dims[1] = data.getCalculationInfo('stackResultDimension');
+                dims[1] = stackResultDim;
             }
 
             function progress(params, data) {
diff --git a/src/scale/Interval.js b/src/scale/Interval.js
index 1f8073c..d342703 100644
--- a/src/scale/Interval.js
+++ b/src/scale/Interval.js
@@ -71,18 +71,6 @@ var IntervalScale = Scale.extend({
     },
 
     /**
-     * @return {Array.<string>}
-     */
-    getTicksLabels: function () {
-        var labels = [];
-        var ticks = this.getTicks();
-        for (var i = 0; i < ticks.length; i++) {
-            labels.push(this.getLabel(ticks[i]));
-        }
-        return labels;
-    },
-
-    /**
      * @param {number} data
      * @param {Object} [opt]
      * @param {number|string} [opt.precision] If 'auto', use nice presision.
diff --git a/src/scale/Scale.js b/src/scale/Scale.js
index f1a781b..a75086c 100644
--- a/src/scale/Scale.js
+++ b/src/scale/Scale.js
@@ -118,18 +118,6 @@ Scale.prototype.setExtent = function (start, end) {
 };
 
 /**
- * @return {Array.<string>}
- */
-Scale.prototype.getTicksLabels = function () {
-    var labels = [];
-    var ticks = this.getTicks();
-    for (var i = 0; i < ticks.length; i++) {
-        labels.push(this.getLabel(ticks[i]));
-    }
-    return labels;
-};
-
-/**
  * When axis extent depends on data and no data exists,
  * axis ticks should not be drawn, which is named 'blank'.
  */
@@ -145,6 +133,13 @@ Scale.prototype.setBlank = function (isBlank) {
     this._isBlank = isBlank;
 };
 
+/**
+ * @abstract
+ * @param {*} tick
+ * @return {string} label of the tick.
+ */
+Scale.prototype.getLabel = null;
+
 
 clazzUtil.enableClassExtend(Scale);
 clazzUtil.enableClassManagement(Scale, {
diff --git a/test/axis-containLabel.html b/test/axis-containLabel.html
new file mode 100644
index 0000000..0331464
--- /dev/null
+++ b/test/axis-containLabel.html
@@ -0,0 +1,287 @@
+<!DOCTYPE>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <link rel="stylesheet" href="lib/reset.css">
+    </head>
+    <body>
+        <style>
+            .test-title {
+                background: #b8d4ff;
+                color: #000;
+            }
+         </style>
+
+        <div class="chart" id="main0"></div>
+        <div class="chart" id="main1"></div>
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('category' + i);
+                    data1.push((Math.random() * 5).toFixed(2) * 1e6);
+                    data3.push((Math.random() + 0.5).toFixed(2) * 1e6);
+                }
+
+                var option = {
+                    backgroundColor: '#fe9',
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    xAxis: {
+                        id: 'xx',
+                        data: xAxisData,
+                        axisTick: {
+                            interval: 4,
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    },
+                    yAxis: {
+                        id: 'yy',
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    },
+                    grid: {
+                        left: 0,
+                        top: 0,
+                        bottom: 0,
+                        right: 0,
+                        containLabel: true
+                    },
+                    dataZoom: [{
+                        type: 'inside',
+                        startValue: 17,
+                        endValue: 24
+                    }],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two',
+                        data: data3
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'main0', {
+                    title: [
+                        'Check containLabel'
+                    ],
+                    option: option,
+                    info: {grid: option.grid},
+                });
+
+                var rotate = 0;
+
+                chart && next();
+
+                function next() {
+                    var nextInterval = rotate % 90 === 0 ? 2000: 70;
+                    rotate = (rotate + 1) % 90;
+                    setTimeout(function () {
+                        chart.setOption({
+                            xAxis: {
+                                id: 'xx',
+                                axisLabel: {
+                                    rotate: rotate
+                                }
+                            },
+                            yAxis: {
+                                id: 'yy',
+                                axisLabel: {
+                                    rotate: rotate
+                                }
+                            }
+                        });
+                        next();
+                    }, nextInterval);
+                }
+
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('category' + i);
+                    data1.push((Math.random() * 5).toFixed(2) * 1e6);
+                    data3.push((Math.random() + 0.5).toFixed(2) * 1e6);
+                }
+
+                var option = {
+                    backgroundColor: '#fe9',
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    xAxis: [{
+                        id: 'x0',
+                        data: xAxisData,
+                        axisTick: {
+                            interval: 4,
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    }, {
+                        id: 'x1',
+                        data: xAxisData,
+                        axisTick: {
+                            interval: 4,
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    }],
+                    yAxis: [{
+                        id: 'y0',
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    }, {
+                        id: 'y1',
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    }],
+                    grid: {
+                        left: 0,
+                        top: 0,
+                        bottom: 0,
+                        right: 0,
+                        containLabel: true
+                    },
+                    dataZoom: [{
+                        type: 'inside',
+                        startValue: 17,
+                        endValue: 24
+                    }],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar3',
+                        xAxisIndex: 1,
+                        yAxisIndex: 1,
+                        type: 'bar',
+                        stack: 'two',
+                        data: data3
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'main1', {
+                    title: [
+                        'Check containLabel'
+                    ],
+                    option: option,
+                    info: {grid: option.grid},
+                });
+
+                var rotate = 0;
+
+                chart && next();
+
+                function next() {
+                    var nextInterval = rotate % 90 === 0 ? 2000: 70;
+                    rotate = (rotate + 1) % 90;
+                    setTimeout(function () {
+                        chart.setOption({
+                            xAxis: [
+                                {id: 'x0', axisLabel: {rotate: rotate}},
+                                {id: 'x1', axisLabel: {rotate: rotate}}
+                            ],
+                            yAxis: [
+                                {id: 'y0', axisLabel: {rotate: rotate}},
+                                {id: 'y1', axisLabel: {rotate: rotate}}
+                            ]
+                        });
+                        next();
+                    }, nextInterval);
+                }
+
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+    </body>
+</html>
\ No newline at end of file
diff --git a/test/axis-interval.html b/test/axis-interval.html
index 988cfa2..b5eed90 100644
--- a/test/axis-interval.html
+++ b/test/axis-interval.html
@@ -38,22 +38,6 @@
 
             require([
                 'echarts'
-                // 'echarts/chart/line',
-                // 'echarts/chart/bar',
-                // 'echarts/chart/pie',
-                // 'echarts/chart/scatter',
-                // 'echarts/chart/map',
-                // 'echarts/chart/parallel',
-                // 'echarts/chart/radar',
-                // 'echarts/component/grid',
-                // 'echarts/component/polar',
-                // 'echarts/component/geo',
-                // 'echarts/component/singleAxis',
-                // 'echarts/component/legend',
-                // 'echarts/component/tooltip',
-                // 'echarts/component/toolbox',
-                // 'echarts/component/visualMap',
-                // 'echarts/component/dataZoom'
             ], function (echarts) {
 
                 option = {
diff --git a/test/axis-interval2.html b/test/axis-interval2.html
new file mode 100644
index 0000000..eb25ff7
--- /dev/null
+++ b/test/axis-interval2.html
@@ -0,0 +1,851 @@
+<!DOCTYPE>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <link rel="stylesheet" href="lib/reset.css">
+    </head>
+    <body>
+        <style>
+            h1 {
+                line-height: 60px;
+                height: 60px;
+                background: #a60;
+                text-align: center;
+                font-weight: bold;
+                color: #eee;
+                font-size: 14px;
+            }
+            .chart {
+                height: 500px;
+            }
+        </style>
+
+        <div class="chart" id="main0"></div>
+        <div class="chart" id="main1"></div>
+        <div class="chart" id="main2"></div>
+        <div class="chart" id="main3"></div>
+        <div class="chart" id="main4"></div>
+        <div class="chart" id="main5"></div>
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data2 = [];
+                var data3 = [];
+
+                var count = 100;
+                for (var i = 0; i < count; i++) {
+
+                    if (i === 14 || i === 20) {
+                        xAxisData.push({
+                            value: '类目' + i,
+                            textStyle: {
+                                color: 'red'
+                            }
+                        });
+                    } else {
+                        xAxisData.push('类目' + i);
+                    }
+
+                    if (i < 5 && i > 1) {
+                        data1.push(0);
+                    }
+                    else {
+                        data1.push(+(Math.random() + 0.5).toFixed(3));
+                    }
+                    data2.push(+(Math.random() + 0.5).toFixed(3));
+                    data3.push(+(Math.random() + 0.5).toFixed(3));
+                }
+
+                var itemStyle = {
+                    normal: {
+                        borderColor: 'white',
+                        borderWidth: 3,
+                        lineStyle: {
+                            width: 1
+                        }
+                    }
+                };
+
+                // var rawData = [[0.877,1.384,0,0,0,1.422,1.363,0.867,0.782,1.025,0.819,0.638,1.192,0.56,1.17,1.056,0.804,0.657,1.157,0.906,1.462,0.856,1.384,0.978,0.766,0.683,1.383,0.65,1.343,1.303,1.298,0.812,0.665,1.182,0.528,0.613,1.101,0.959,0.997,1.381,1.172,1.01,1.23,0.596,1.256,1.406,1.172,0.85,1.194,1.313,1.142,0.985,1.059,1.07,1.205,1.359,0.93,0.514,1.197,1.259,1.225,1.371,0.825,0.967,0.569,1.432,0.892,1.36,0.644,1.096,1.006,0.613,0.549,1.263,1.203,0.556,1.044,1.338,0.8,1.137, [...]
+                // data1 = rawData[0];
+                // data2 = rawData[1];
+                // data3 = rawData[2];
+
+                var option = {
+                    legend: {
+                        data: ['line', 'line2', 'line3']
+                    },
+                    visualMap: null, // 用于测试 option 中含有null的情况。
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'line'
+                        }
+                    },
+                    xAxis: {
+                        // data: ['类目1', '类目2', '类目3', '类目4', '类目5',]
+                        data: xAxisData,
+                        boundaryGap: false,
+                        // inverse: true,
+                        splitArea: {
+                            show: false
+                        },
+                        splitLine: {
+                            show: false
+                        },
+                        axisLabel: {
+                            // showMaxLabel: true,
+                            // showMinLabel: true
+                        }
+                    },
+                    grid: {
+                        left: '10%',
+                        right: '10%'
+                    },
+                    yAxis: {
+                        axisLabel: {
+                            textStyle: {
+                                color: 'red'
+                            }
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    },
+                    dataZoom: [{
+                        type: 'inside',
+                        // start: 10,
+                        // end: 12
+                        startValue: 11,
+                        endValue: 85
+                    }, {
+                        type: 'slider',
+                        // start: 10,
+                        // end: 12
+                        startValue: 11,
+                        endValue: 85
+                    }],
+                    // animationDurationUpdate: 2000,
+                    // animation: false,
+                    series: [
+                        null,  // 用于测试 option 中含有null的情况。
+                        {
+                            name: 'line',
+                            type: 'line',
+                            stack: 'all',
+                            symbol: 'path://M164,210.677v33.47l154.656,66.356L468,243.681v-33.004H164L164,210.677z M164,282.255L164,282.255v134.76h304V282.061l-149.012,66.615L164,282.255L164,282.255z',
+                            symbolKeepAspect: true,
+                            symbolSize: 40,
+                            data: data1,
+                            itemStyle: itemStyle,
+                            label: {
+                                normal: {
+                                    show: true,
+                                    fontSize: 12
+                                }
+                            },
+                            lineStyle: {
+                                normal: {
+                                    shadowBlur: 4,
+                                    shadowOffsetX: 3,
+                                    shadowOffsetY: 3
+                                }
+                            },
+                            step: 'end'
+                        },
+                        {
+                            label: {
+                                normal: {
+                                    show: true,
+                                    position: 'outside'
+                                }
+                            },
+                            name: 'line2',
+                            type: 'line',
+                            stack: 'all',
+                            symbol: 'circle',
+                            symbolSize: 10,
+                            data: data2,
+                            itemStyle: itemStyle,
+                            step: 'end'
+                        },
+                        {
+                            name: 'line3',
+                            type: 'line',
+                            stack: 'all',
+                            symbol: 'triangle',
+                            symbolSize: 10,
+                            data: data3,
+                            itemStyle: itemStyle,
+                            step: 'end'
+                        }
+                    ]
+                };
+
+                chart = myChart = testHelper.create(echarts, 'main0', {
+                    title: [
+                        '(0) Move the slider zoom, check the tick and symbol animation stable (not change ticks selection)',
+                        '(1) Adjust (zoom) the slider zoom to the extent of just change the tick interval.',
+                        '(2) Move the slider zoom, CHECK whether the interval is stable (no jitter).'
+                    ],
+                    option: option
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data2 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('类目' + i);
+                    data1.push((Math.random() * 5).toFixed(2));
+                    data2.push(-Math.random().toFixed(2));
+                    data3.push((Math.random() + 0.5).toFixed(2));
+                }
+
+                var itemStyle = {
+                    normal: {
+                        barBorderRadius: 5,
+                        label: {
+                            show: true,
+                            position: 'outside'
+                        }
+                    },
+                    emphasis: {
+                        label: {
+                            position: 'outside'
+                        },
+                        barBorderColor: '#fff',
+                        barBorderWidth: 1,
+                        shadowBlur: 10,
+                        shadowOffsetX: 0,
+                        shadowOffsetY: 0,
+                        shadowColor: 'rgba(0,0,0,0.5)'
+                    }
+                };
+
+                var option = {
+                    backgroundColor: '#eef',
+                    legend: {
+                    },
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    xAxis: {
+                        data: xAxisData,
+                        name: '横轴',
+                        silent: false,
+                        inverse: true,
+                        axisTick: {
+                            alignWithLabel: true
+                        },
+                        // axisLabel: {
+                        //     show: false
+                        // },
+                        // axisTick: {
+                        //     show: false
+                        // },
+                        axisLine: {
+                            onZero: true,
+                            // lineStyle: {
+                            //     width: 40
+                            // }
+                        },
+                        splitLine: {
+                            show: true,
+                            lineStyle: {
+                                color: 'green'
+                            }
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    },
+                    yAxis: {
+                        inverse: true,
+                        // axisLabel: {
+                        //     show: false
+                        // },
+                        axisTick: {
+                            show: false
+                        },
+                        // splitLine: {
+                        //     show: false
+                        // },
+                        splitArea: {
+                            show: false
+                        }
+                    },
+                    dataZoom: [{
+                        type: 'inside'
+                    }, {}],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        itemStyle: itemStyle,
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar2',
+                        type: 'bar',
+                        stack: 'one',
+                        itemStyle: itemStyle,
+                        cursor: 'default',
+                        data: data2
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two',
+                        itemStyle: itemStyle,
+                        data: data3
+                    }]
+                };
+
+                chart = myChart = testHelper.create(echarts, 'main1', {
+                    title: [
+                        '(0) Zoom and check the splitLine(green) and splitArea when axis interval exists',
+                        '(1) Check not split a single data item when odd category tick interval'
+                    ],
+                    option: option
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data2 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('类目' + i);
+                    data1.push((Math.random() * 5).toFixed(2));
+                    data2.push(-Math.random().toFixed(2));
+                    data3.push((Math.random() + 0.5).toFixed(2));
+                }
+
+                var itemStyle = {
+                    normal: {
+                        barBorderRadius: 5,
+                        label: {
+                            show: true,
+                            position: 'outside'
+                        }
+                    },
+                    emphasis: {
+                        label: {
+                            position: 'outside'
+                        },
+                        barBorderColor: '#fff',
+                        barBorderWidth: 1,
+                        shadowBlur: 10,
+                        shadowOffsetX: 0,
+                        shadowOffsetY: 0,
+                        shadowColor: 'rgba(0,0,0,0.5)'
+                    }
+                };
+
+                var option = {
+                    backgroundColor: '#eef',
+                    legend: {
+                    },
+                    tooltip: {
+                    },
+                    xAxis: {
+                        data: xAxisData,
+                        name: '横轴',
+                        silent: false,
+                        axisTick: {
+                            alignWithLabel: true
+                        },
+                        // axisLabel: {
+                        //     show: false
+                        // },
+                        // axisTick: {
+                        //     show: false
+                        // },
+                        axisLine: {
+                            onZero: true
+                        },
+                        splitLine: {
+                            show: true,
+                            lineStyle: {
+                                color: 'green'
+                            }
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    },
+                    yAxis: {
+                        inverse: true,
+                        // axisLabel: {
+                        //     show: false
+                        // },
+                        axisTick: {
+                            show: false
+                        },
+                        // splitLine: {
+                        //     show: false
+                        // },
+                        splitArea: {
+                            show: false
+                        }
+                    },
+                    animationDurationUpdate: 800,
+                    dataZoom: [{
+                        type: 'inside',
+                        startValue: 17,
+                        endValue: 24,
+                        zoomLock: true
+                    }, {
+                        startValue: 17,
+                        endValue: 24,
+                        zoomLock: true
+                    }],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        itemStyle: itemStyle,
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar2',
+                        type: 'bar',
+                        stack: 'one',
+                        itemStyle: itemStyle,
+                        cursor: 'default',
+                        data: data2
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two',
+                        itemStyle: itemStyle,
+                        data: data3
+                    }]
+                };
+
+                chart = myChart = testHelper.create(echarts, 'main2', {
+                    title: [
+                        '(0) Move and check splitArea and splitLine(green) animation (zoom locked)'
+                    ],
+                    option: option
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('类目' + i);
+                    data1.push((Math.random() * 5).toFixed(2));
+                    data3.push((Math.random() + 0.5).toFixed(2));
+                }
+
+                var itemStyle = {
+                    normal: {
+                        barBorderRadius: 5,
+                        label: {
+                            show: true,
+                            position: 'outside'
+                        }
+                    },
+                    emphasis: {
+                        label: {
+                            position: 'outside'
+                        },
+                        barBorderColor: '#fff',
+                        barBorderWidth: 1,
+                        shadowBlur: 10,
+                        shadowOffsetX: 0,
+                        shadowOffsetY: 0,
+                        shadowColor: 'rgba(0,0,0,0.5)'
+                    }
+                };
+
+                var option = {
+                    backgroundColor: '#eef',
+                    legend: {
+                    },
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    xAxis: {
+                        data: xAxisData,
+                        name: '横轴',
+                        silent: false,
+                        axisLine: {
+                            onZero: true
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    },
+                    yAxis: {
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    },
+                    dataZoom: [{
+                        type: 'inside',
+                        startValue: 17,
+                        endValue: 24,
+                        zoomLock: true
+                    }, {
+                        startValue: 17,
+                        endValue: 24,
+                        zoomLock: true
+                    }],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        itemStyle: itemStyle,
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two',
+                        itemStyle: itemStyle,
+                        data: data3
+                    }]
+                };
+
+                chart = myChart = testHelper.create(echarts, 'main3', {
+                    title: [
+                        'alignWithTick: default (false), and boundaryGap: default (true)'
+                    ],
+                    option: option
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('c' + i);
+                    data1.push((Math.random() * 5).toFixed(2));
+                    data3.push((Math.random() + 0.5).toFixed(2));
+                }
+
+                var option = {
+                    legend: {
+                    },
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    xAxis: {
+                        data: xAxisData,
+                        axisTick: {
+                            interval: 4,
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    },
+                    yAxis: {
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    },
+                    dataZoom: [{
+                        type: 'inside',
+                        startValue: 17,
+                        endValue: 24
+                    }, {
+                        startValue: 17,
+                        endValue: 24
+                    }],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two',
+                        data: data3
+                    }]
+                };
+
+                chart = myChart = testHelper.create(echarts, 'main4', {
+                    title: [
+                        'axisTick.interval is different from axisLabel.interval'
+                    ],
+                    option: option
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'echarts'
+            ], function (echarts) {
+
+                var xAxisData = [];
+                var data1 = [];
+                var data3 = [];
+
+                for (var i = 0; i < 100; i++) {
+                    xAxisData.push('c' + i);
+                    data1.push((Math.random() * 5).toFixed(2));
+                    data3.push((Math.random() + 0.5).toFixed(2));
+                }
+
+                var option = {
+                    legend: {
+                    },
+                    tooltip: {
+                        trigger: 'axis',
+                        axisPointer: {
+                            type: 'shadow'
+                        }
+                    },
+                    xAxis: [{
+                        data: xAxisData,
+                        name: 'axisLabel.interval is function',
+                        nameLocation: 'middle',
+                        nameGap: 20,
+                        axisTick: {
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                            interval: function (categoryIdx, categoryValue) {
+                                return categoryIdx % 5 === 0;
+                            }
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    }, {
+                        data: xAxisData,
+                        name: 'axisTick.interval is function',
+                        nameLocation: 'middle',
+                        nameGap: 20,
+                        gridIndex: 1,
+                        axisTick: {
+                            interval: function (categoryIdx, categoryValue) {
+                                return categoryIdx % 5 === 0;
+                            },
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                        },
+                        splitArea: {
+                            show: true
+                        }
+                    }],
+                    yAxis: [{
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    }, {
+                        gridIndex: 1,
+                        axisTick: {
+                            show: false
+                        },
+                        splitArea: {
+                            show: false
+                        }
+                    }],
+                    grid: [{
+                        bottom: '60%'
+                    }, {
+                        top: '52%',
+                        bottom: 80
+                    }],
+                    dataZoom: [{
+                        type: 'inside',
+                        xAxisIndex: [0, 1],
+                        startValue: 17,
+                        endValue: 24
+                    }, {
+                        xAxisIndex: [0, 1],
+                        startValue: 17,
+                        endValue: 24
+                    }],
+                    series: [{
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one',
+                        cursor: 'move',
+                        data: data1
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two',
+                        data: data3
+                    }, {
+                        name: 'bar',
+                        type: 'bar',
+                        stack: 'one1',
+                        cursor: 'move',
+                        xAxisIndex: 1,
+                        yAxisIndex: 1,
+                        data: data1
+                    }, {
+                        name: 'bar3',
+                        type: 'bar',
+                        stack: 'two1',
+                        xAxisIndex: 1,
+                        yAxisIndex: 1,
+                        data: data3
+                    }]
+                };
+
+                chart = myChart = testHelper.create(echarts, 'main5', {
+                    title: [
+                        'axisLabel.interval and axisTick.interval are function'
+                    ],
+                    option: option,
+                    info: {xAxis: option.xAxis}
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    </body>
+</html>
\ No newline at end of file
diff --git a/test/axis-lastLabel.html b/test/axis-lastLabel.html
index 689da66..fbbb8e9 100644
--- a/test/axis-lastLabel.html
+++ b/test/axis-lastLabel.html
@@ -4,6 +4,7 @@
         <script src="lib/esl.js"></script>
         <script src="lib/config.js"></script>
         <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
         <meta name="viewport" content="width=device-width, initial-scale=1" />
         <link rel="stylesheet" href="lib/reset.css" />
     </head>
@@ -18,19 +19,14 @@
                 font-size: 14px;
                 margin: 0;
             }
-            .chart {
-                height: 350px;
-            }
         </style>
 
-        <h1>xAxis: {showMinLabel: null, showMaxLabel: null (auto)}. The last x label should not be displayed (overlap).</h1>
-        <div class="chart" id="chart-1"></div>
 
-        <h1>xAxis: {showMinLabel: null, showMaxLabel: null (auto)}. The last x label shoule be displayed (not overlap).</h1>
-        <div class="chart" id="chart0"></div>
 
-        <h1>xAxis: {showMinLabel: true, showMaxLabel: true}, yAxis: {showMaxLabel: false}. The last x label should be displayed. 100 on y should not be displayed.</h1>
+        <div class="chart" id="chart-1"></div>
+        <div class="chart" id="chart0"></div>
         <div class="chart" id="chart1"></div>
+        <div class="chart" id="chart2"></div>
 
 
 
@@ -46,26 +42,12 @@
             require([
                 'data/rainfall.json',
                 'echarts'
-                // 'echarts/chart/line',
-                // 'echarts/component/legend',
-                // 'echarts/component/grid',
-                // 'echarts/component/tooltip',
-                // 'echarts/component/title',
-                // 'echarts/component/dataZoom',
-                // 'echarts/scale/Time'
             ], function (rainfallData, echarts) {
 
-                var chart = echarts.init(document.getElementById('chart-1'), null, {
-
-                });
-
-                chart.setOption({
+                var option = {
                     tooltip : {
                         trigger: 'axis'
                     },
-                    grid: {
-                        bottom: 150
-                    },
                     dataZoom: {
                         show: true,
                         realtime: true,
@@ -99,6 +81,15 @@
                             })
                         }
                     ]
+                };
+
+                var chart = testHelper.create(echarts, 'chart-1', {
+                    title: [
+                        'The last x label should not be displayed (overlap).'
+                    ],
+                    option: option,
+                    info: option.xAxis,
+                    infoKey: 'xAxis'
                 });
             })
 
@@ -116,20 +107,13 @@
             require([
                 'data/rainfall.json',
                 'echarts'
-                // 'echarts/chart/line',
-                // 'echarts/component/legend',
-                // 'echarts/component/grid',
-                // 'echarts/component/tooltip',
-                // 'echarts/component/title',
-                // 'echarts/component/dataZoom',
-                // 'echarts/scale/Time'
             ], function (rainfallData, echarts) {
 
                 var chart = echarts.init(document.getElementById('chart0'), null, {
 
                 });
 
-                chart.setOption({
+                var option = {
                     tooltip : {
                         trigger: 'axis'
                     },
@@ -170,6 +154,15 @@
                             })
                         }
                     ]
+                };
+
+                var chart = testHelper.create(echarts, 'chart0', {
+                    title: [
+                        'The last x label shoule be displayed (not overlap).'
+                    ],
+                    option: option,
+                    info: option.xAxis,
+                    infoKey: 'xAxis'
                 });
             })
 
@@ -188,26 +181,16 @@
             require([
                 'data/rainfall.json',
                 'echarts'
-                // 'echarts/chart/line',
-                // 'echarts/component/legend',
-                // 'echarts/component/grid',
-                // 'echarts/component/tooltip',
-                // 'echarts/component/title',
-                // 'echarts/component/dataZoom',
-                // 'echarts/scale/Time'
             ], function (rainfallData, echarts) {
 
                 var chart = echarts.init(document.getElementById('chart1'), null, {
 
                 });
 
-                chart.setOption({
+                var option = {
                     tooltip : {
                         trigger: 'axis'
                     },
-                    grid: {
-                        bottom: 150
-                    },
                     dataZoom: {
                         show: true,
                         realtime: true,
@@ -249,6 +232,17 @@
                             })
                         }
                     ]
+                };
+
+                var chart = testHelper.create(echarts, 'chart1', {
+                    title: [
+                        'The last x label should be displayed. The top tick on y should not be displayed.'
+                    ],
+                    option: option,
+                    info: {
+                        yAxis: option.yAxis,
+                        xAxis: option.xAxis
+                    }
                 });
             })
 
@@ -259,5 +253,89 @@
 
 
 
+
+
+
+
+
+
+
+
+        <script>
+
+            require([
+                'data/rainfall.json',
+                'echarts'
+            ], function (rainfallData, echarts) {
+
+                var chart = echarts.init(document.getElementById('chart1'), null, {
+
+                });
+
+                var option = {
+                    tooltip: {
+                        trigger: 'axis'
+                    },
+                    dataZoom: {
+                        show: true,
+                        realtime: true,
+                        startValue: '2009-09-20 12:00',
+                        end: 100
+                    },
+                    xAxis: {
+                        axisTick: {
+                            alignWithLabel: true
+                        },
+                        axisLabel: {
+                            showMaxLabel: true,
+                            showMinLabel: true,
+                            formatter: function (value) {
+                                return echarts.format.formatTime('yyyy-MM-dd hh:mm:ss', value);
+                            }
+                        },
+                        data: rainfallData.flow.map(function (val, idx) {
+                            return rainfallData.category[idx];
+                        })
+                    },
+                    yAxis: {
+                        name: '流量(m^3/s)',
+                        axisLabel: {
+                            inside: true,
+                            showMaxLabel: false,
+                            formatter: '{value}\n'
+                        }
+                    },
+                    series: [
+                        {
+                            name: '流量',
+                            type: 'line',
+                            symbol: 'none',
+                            itemStyle: {normal: {areaStyle: {type: 'default'}}},
+                            data: rainfallData.flow.map(function (val, idx) {
+                                return val;
+                            })
+                        }
+                    ]
+                };
+
+                var chart = testHelper.create(echarts, 'chart2', {
+                    title: [
+                        'category xAxis:The min and max x label should be displayed.'
+                    ],
+                    option: option,
+                    info: {
+                        axisLabel: option.xAxis.axisLabel,
+                        axisTick: option.xAxis.axisTick
+                    },
+                    infoKey: 'xAxis'
+                });
+            })
+
+        </script>
+
+
+
+
+
     </body>
 </html>
\ No newline at end of file
diff --git a/test/bar.html b/test/bar.html
index 352bacf..d9ece93 100644
--- a/test/bar.html
+++ b/test/bar.html
@@ -174,6 +174,9 @@
                         // },
                         axisLine: {
                             onZero: true
+                            // lineStyle: {
+                            //     width: 5
+                            // }
                         },
                         splitLine: {
                             show: true
@@ -187,6 +190,11 @@
                         // axisLabel: {
                         //     show: false
                         // },
+                        // axisLine: {
+                        //     lineStyle: {
+                        //         width: 5
+                        //     }
+                        // },
                         axisTick: {
                             show: false
                         },
diff --git a/test/line.html b/test/line.html
index f0f2a6d..9ebf506 100644
--- a/test/line.html
+++ b/test/line.html
@@ -49,8 +49,6 @@
                         xAxisData.push('类目' + i);
                     }
 
-
-
                     if (i < 5 && i > 1) {
                         data1.push(0);
                     }
@@ -80,25 +78,27 @@
                 };
 
                 chart.setOption({
-
                     title: {
-                        text: '折线图{a|asdf}Step line',
-                        subtext: 'subtitle',
-                        backgroundColor: '#ee99aa',
-                        borderRadius: 5,
-                        padding: 10,
-                        textStyle: {
-                            rich: {
-                                a: {
-                                    color: '#338844',
-                                    fontSize: 30,
-                                    borderColor: '#119955',
-                                    borderWidth: 3,
-                                    backgroundColor: '#445566'
-                                }
-                            }
-                        }
+                        text: 'tick 14 and 20 should be red (has textStyle)'
                     },
+                    // title: {
+                    //     text: '折线图{a|asdf}Step line',
+                    //     subtext: 'subtitle',
+                    //     backgroundColor: '#ee99aa',
+                    //     borderRadius: 5,
+                    //     padding: 10,
+                    //     textStyle: {
+                    //         rich: {
+                    //             a: {
+                    //                 color: '#338844',
+                    //                 fontSize: 30,
+                    //                 borderColor: '#119955',
+                    //                 borderWidth: 3,
+                    //                 backgroundColor: '#445566'
+                    //             }
+                    //         }
+                    //     }
+                    // },
 
                     legend: {
                         data: ['line', 'line2', 'line3']
@@ -122,8 +122,8 @@
                             show: false
                         },
                         axisLabel: {
-                            showMaxLabel: true,
-                            showMinLabel: true
+                            // showMaxLabel: true,
+                            // showMinLabel: true
                         }
                     },
                     grid: {
@@ -140,11 +140,17 @@
                             show: true
                         }
                     },
-                    dataZoom: {
+                    dataZoom: [{
                         type: 'inside',
-                        start: 10,
-                        end: 30
-                    },
+                        // start: 10,
+                        // end: 30
+                        startValue: 11,
+                        endValue: 85
+                    }, {
+                        type: 'slider',
+                        startValue: 11,
+                        endValue: 85
+                    }],
                     // animation: false,
                     series: [null,  // 用于测试 option 中含有null的情况。
                     {
diff --git a/test/timeline-layout.html b/test/timeline-layout.html
index 9738cb0..81714dc 100644
--- a/test/timeline-layout.html
+++ b/test/timeline-layout.html
@@ -32,14 +32,9 @@
 
             require([
                 'echarts'
-                // 'zrender/core/util',
-                // 'echarts/chart/bar',
-                // 'echarts/component/legend',
-                // 'echarts/component/grid',
-                // 'echarts/component/tooltip',
-                // 'echarts/component/timeline'
             ], function (echarts) {
                 var zrUtil = echarts.util;
+
                 makeChart({
                     timeline: {
                         label: {
@@ -63,7 +58,8 @@
                         symbolRotate: 30
                     }
                 });
-                makeChart({timeline: {inverse: true, controlStyle: {position: 'right'}}});
+
+                makeChart({timeline: {inverse: true, controlStyle: {position: 'right'}}, width: 400});
 
                 makeChart({timeline: {orient: 'vertical', x: 0, y: 10, width: 55, height: '80%'}});
                 makeChart({timeline: {orient: 'vertical', inverse: true, rewind: true, x: 0, y: 10, width: 55, height: '80%'}});
@@ -118,7 +114,11 @@
                     var containerEl = document.getElementById('main');
                     var el = document.createElement('div');
                     el.className = 'block';
-                    el.innerHTML = '<div class="ec"></div><label>'
+                    var widthString = '';
+                    if (opt.width != null) {
+                        widthString = ' style="width:' + opt.width + 'px" ';
+                    }
+                    el.innerHTML = '<div class="ec" ' + widthString + '></div><label>'
                         + encodeHTML(JSON.stringify(opt)) + '</label>';
                     containerEl.appendChild(el);
 

-- 
To stop receiving notification emails like this one, please contact
sushuang@apache.org.

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