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

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

commit 580972089a8d73394247c1f82fe7c6c6056d849f
Author: pissang <[email protected]>
AuthorDate: Wed Apr 22 22:52:16 2020 +0800

    feat: add label manager for each series to layout label.
---
 src/ExtensionAPI.ts                  |  45 +++---
 src/chart/funnel/FunnelView.ts       |  10 +-
 src/chart/graph/GraphView.ts         |   2 +
 src/chart/sunburst/SunburstPiece.ts  |   4 +-
 src/chart/sunburst/SunburstSeries.ts |   5 +-
 src/chart/tree/TreeView.ts           |   2 +
 src/echarts.ts                       |  32 +++-
 src/model/Model.ts                   |  64 +++++---
 src/model/mixin/dataFormat.ts        |  30 ++--
 src/stream/Scheduler.ts              |  16 +-
 src/util/LabelManager.ts             | 286 +++++++++++++++++++++++++++++++++++
 src/util/graphic.ts                  | 107 +++++++++++--
 src/util/types.ts                    |  49 +++++-
 test/animation-additive.html         | 162 ++++++++++++++++++++
 test/bar-stack.html                  |  31 +---
 test/graph-label-rotate.html         |  14 +-
 test/label-overlap.html              | 207 +++++++++++++++++++++++++
 17 files changed, 936 insertions(+), 130 deletions(-)

diff --git a/src/ExtensionAPI.ts b/src/ExtensionAPI.ts
index dae64dd..cb72d65 100644
--- a/src/ExtensionAPI.ts
+++ b/src/ExtensionAPI.ts
@@ -23,32 +23,33 @@ import {CoordinateSystemMaster} from 
'./coord/CoordinateSystem';
 import Element from 'zrender/src/Element';
 import ComponentModel from './model/Component';
 
-const availableMethods = {
-    getDom: 1,
-    getZr: 1,
-    getWidth: 1,
-    getHeight: 1,
-    getDevicePixelRatio: 1,
-    dispatchAction: 1,
-    isDisposed: 1,
-    on: 1,
-    off: 1,
-    getDataURL: 1,
-    getConnectedDataURL: 1,
-    getModel: 1,
-    getOption: 1,
-    getViewOfComponentModel: 1,
-    getViewOfSeriesModel: 1,
-    getId: 1
-};
-
-interface ExtensionAPI extends Pick<EChartsType, keyof typeof 
availableMethods> {}
+const availableMethods: (keyof EChartsType)[] = [
+    'getDom',
+    'getZr',
+    'getWidth',
+    'getHeight',
+    'getDevicePixelRatio',
+    'dispatchAction',
+    'isDisposed',
+    'on',
+    'off',
+    'getDataURL',
+    'getConnectedDataURL',
+    'getModel',
+    'getOption',
+    'getViewOfComponentModel',
+    'getViewOfSeriesModel',
+    'getId',
+    'updateLabelLayout'
+];
+
+interface ExtensionAPI extends Pick<EChartsType, (typeof 
availableMethods)[number]> {}
 
 abstract class ExtensionAPI {
 
     constructor(ecInstance: EChartsType) {
-        zrUtil.each(availableMethods, function (v, name: string) {
-            (this as any)[name] = zrUtil.bind((ecInstance as any)[name], 
ecInstance);
+        zrUtil.each(availableMethods, function (methodName: string) {
+            (this as any)[methodName] = zrUtil.bind((ecInstance as 
any)[methodName], ecInstance);
         }, this);
     }
 
diff --git a/src/chart/funnel/FunnelView.ts b/src/chart/funnel/FunnelView.ts
index 47e1209..2cb9297 100644
--- a/src/chart/funnel/FunnelView.ts
+++ b/src/chart/funnel/FunnelView.ts
@@ -23,7 +23,8 @@ import FunnelSeriesModel, {FunnelDataItemOption} from 
'./FunnelSeries';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import List from '../../data/List';
-import { ColorString } from '../../util/types';
+import { ColorString, LabelOption } from '../../util/types';
+import Model from '../../model/Model';
 
 const opacityAccessPath = ['itemStyle', 'opacity'] as const;
 
@@ -109,7 +110,8 @@ class FunnelPiece extends graphic.Group {
         const visualColor = data.getItemVisual(idx, 'style').fill as 
ColorString;
 
         graphic.setLabelStyle(
-            labelText, labelModel, labelHoverModel,
+            // position will not be used in setLabelStyle
+            labelText, labelModel as Model<LabelOption>, labelHoverModel as 
Model<LabelOption>,
             {
                 labelFetcher: data.hostModel as FunnelSeriesModel,
                 labelDataIndex: idx,
@@ -151,10 +153,6 @@ class FunnelPiece extends graphic.Group {
             z2: 10
         });
 
-        labelText.ignore = !labelModel.get('show');
-        const labelTextEmphasisState = labelText.ensureState('emphasis');
-        labelTextEmphasisState.ignore = !labelHoverModel.get('show');
-
         labelLine.ignore = !labelLineModel.get('show');
         const labelLineEmphasisState = labelLine.ensureState('emphasis');
         labelLineEmphasisState.ignore = !labelLineHoverModel.get('show');
diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts
index 7d604a3..8f83896 100644
--- a/src/chart/graph/GraphView.ts
+++ b/src/chart/graph/GraphView.ts
@@ -437,6 +437,8 @@ class GraphView extends ChartView {
                 this._updateNodeAndLinkScale();
                 adjustEdge(seriesModel.getGraph(), 
getNodeGlobalScale(seriesModel));
                 this._lineDraw.updateLayout();
+                // Only update label layout on zoom
+                api.updateLabelLayout();
             });
     }
 
diff --git a/src/chart/sunburst/SunburstPiece.ts 
b/src/chart/sunburst/SunburstPiece.ts
index 86bf38a..9fbb3fd 100644
--- a/src/chart/sunburst/SunburstPiece.ts
+++ b/src/chart/sunburst/SunburstPiece.ts
@@ -218,9 +218,7 @@ class SunburstPiece extends graphic.Group {
         const labelHoverModel = itemModel.getModel(['emphasis', 'label']);
 
         let text = zrUtil.retrieve(
-            seriesModel.getFormattedLabel(
-                this.node.dataIndex, state, null, null, 'label'
-            ),
+            seriesModel.getFormattedLabel(this.node.dataIndex, state),
             this.node.name
         );
         if (getLabelAttr('show') === false) {
diff --git a/src/chart/sunburst/SunburstSeries.ts 
b/src/chart/sunburst/SunburstSeries.ts
index be7b077..327a1ab 100644
--- a/src/chart/sunburst/SunburstSeries.ts
+++ b/src/chart/sunburst/SunburstSeries.ts
@@ -139,10 +139,7 @@ export interface SunburstSeriesOption extends 
SeriesOption, CircleLayoutOptionMi
 interface SunburstSeriesModel {
     getFormattedLabel(
         dataIndex: number,
-        state?: 'emphasis' | 'normal' | 'highlight' | 'downplay',
-        dataType?: string,
-        dimIndex?: number,
-        labelProp?: string
+        state?: 'emphasis' | 'normal' | 'highlight' | 'downplay'
     ): string
 }
 class SunburstSeriesModel extends SeriesModel<SunburstSeriesOption> {
diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts
index 2d16448..1484889 100644
--- a/src/chart/tree/TreeView.ts
+++ b/src/chart/tree/TreeView.ts
@@ -351,6 +351,8 @@ class TreeView extends ChartView {
                     originY: e.originY
                 });
                 this._updateNodeAndLinkScale(seriesModel);
+                // Only update label layout on zoom
+                api.updateLabelLayout();
             });
     }
 
diff --git a/src/echarts.ts b/src/echarts.ts
index 9711a77..ecaa352 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -71,6 +71,7 @@ import IncrementalDisplayable from 
'zrender/src/graphic/IncrementalDisplayable';
 import 'zrender/src/canvas/canvas';
 import { seriesSymbolTask, dataSymbolTask } from './visual/symbol';
 import { getVisualFromData, getItemVisualFromData } from './visual/helper';
+import LabelManager from './util/LabelManager';
 
 declare let global: any;
 type ModelFinder = modelUtil.ModelFinder;
@@ -223,6 +224,8 @@ class ECharts extends Eventful {
 
     private _loadingFX: LoadingEffect;
 
+    private _labelManager: LabelManager;
+
     private [OPTION_UPDATED]: boolean | {silent: boolean};
     private [IN_MAIN_PROCESS]: boolean;
     private [CONNECT_STATUS_KEY]: ConnectStatus;
@@ -287,6 +290,8 @@ class ECharts extends Eventful {
 
         this._messageCenter = new MessageCenter();
 
+        this._labelManager = new LabelManager();
+
         // Init mouse events
         this._initEvents();
 
@@ -331,7 +336,7 @@ class ECharts extends Eventful {
             let remainTime = TEST_FRAME_REMAIN_TIME;
             const ecModel = this._model;
             const api = this._api;
-            scheduler.unfinished = +false;
+            scheduler.unfinished = false;
             do {
                 const startTime = +new Date();
 
@@ -1050,6 +1055,12 @@ class ECharts extends Eventful {
         triggerUpdatedEvent.call(this, silent);
     }
 
+    updateLabelLayout() {
+        const labelManager = this._labelManager;
+        labelManager.updateLayoutConfig(this._api);
+        labelManager.layout();
+    }
+
     appendData(params: {
         seriesIndex: number,
         data: any
@@ -1077,7 +1088,7 @@ class ECharts extends Eventful {
         // graphic elements have to be changed, which make the usage of
         // `appendData` meaningless.
 
-        this._scheduler.unfinished = +true;
+        this._scheduler.unfinished = true;
     }
 
 
@@ -1637,7 +1648,11 @@ class ECharts extends Eventful {
         ): void {
             // Render all charts
             const scheduler = ecIns._scheduler;
-            let unfinished: number;
+            const labelManager = ecIns._labelManager;
+
+            labelManager.clearLabels();
+
+            let unfinished: boolean;
             ecModel.eachSeries(function (seriesModel) {
                 const chartView = ecIns._chartsMap[seriesModel.__viewId];
                 chartView.__alive = true;
@@ -1649,7 +1664,7 @@ class ECharts extends Eventful {
                     renderTask.dirty();
                 }
 
-                unfinished |= 
+renderTask.perform(scheduler.getPerformArgs(renderTask));
+                unfinished = 
renderTask.perform(scheduler.getPerformArgs(renderTask)) || unfinished;
 
                 chartView.group.silent = !!seriesModel.get('silent');
 
@@ -1658,8 +1673,15 @@ class ECharts extends Eventful {
                 updateBlend(seriesModel, chartView);
 
                 updateHoverEmphasisHandler(chartView);
+
+                // Add albels.
+                labelManager.addLabelsOfSeries(chartView);
             });
-            scheduler.unfinished |= unfinished;
+
+            scheduler.unfinished = unfinished || scheduler.unfinished;
+
+            labelManager.updateLayoutConfig(api);
+            labelManager.layout();
 
             // If use hover layer
             updateHoverLayerStatus(ecIns, ecModel);
diff --git a/src/model/Model.ts b/src/model/Model.ts
index bd3e282..5660029 100644
--- a/src/model/Model.ts
+++ b/src/model/Model.ts
@@ -17,7 +17,6 @@
 * under the License.
 */
 
-import * as zrUtil from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
 import {
     enableClassExtend,
@@ -33,8 +32,7 @@ import {ItemStyleMixin} from './mixin/itemStyle';
 import GlobalModel from './Global';
 import { ModelOption } from '../util/types';
 import { Dictionary } from 'zrender/src/core/types';
-
-const mixin = zrUtil.mixin;
+import { mixin, clone, merge, extend, isFunction } from 
'zrender/src/core/util';
 
 // Since model.option can be not only `Dictionary` but also primary types,
 // we do this conditional type to avoid getting type 'never';
@@ -52,20 +50,11 @@ class Model<Opt extends ModelOption = ModelOption> {    // 
TODO: TYPE use unkown
     // subclass overrided filed will be overwritten by this
     // class. That is, they should not be initialized here.
 
-    /**
-     * @readOnly
-     */
     parentModel: Model;
 
-    /**
-     * @readOnly
-     */
-    ecModel: GlobalModel;;
+    ecModel: GlobalModel;
 
-    /**
-     * @readOnly
-     */
-    option: Opt;
+    option: Opt;    // TODO Opt should only be object.
 
     constructor(option?: Opt, parentModel?: Model, ecModel?: GlobalModel) {
         this.parentModel = parentModel;
@@ -89,7 +78,7 @@ class Model<Opt extends ModelOption = ModelOption> {    // 
TODO: TYPE use unkown
      * Merge the input option to me.
      */
     mergeOption(option: Opt, ecModel?: GlobalModel): void {
-        zrUtil.merge(this.option, option, true);
+        merge(this.option, option, true);
     }
 
     // FIXME:TS consider there is parentModel,
@@ -169,6 +158,47 @@ class Model<Opt extends ModelOption = ModelOption> {    // 
TODO: TYPE use unkown
     }
 
     /**
+     * Squash option stack into one.
+     * parentModel will be removed after squashed.
+     *
+     * NOTE: resolveParentPath will not be applied here for simplicity. DON'T 
use this function
+     * if resolveParentPath is modified.
+     *
+     * @param deepMerge If do deep merge. Default to be false.
+     */
+    squash(
+        deepMerge?: boolean,
+        handleCallback?: (func: () => object) => object
+    ) {
+        const optionStack = [];
+        let model: Model = this;
+        while (model) {
+            if (model.option) {
+                optionStack.push(model.option);
+            }
+            model = model.parentModel;
+        }
+
+        const newOption = {} as Opt;
+        let option;
+        while (option = optionStack.pop()) {    // Top down merge
+            if (isFunction(option) && handleCallback) {
+                option = handleCallback(option);
+            }
+            if (deepMerge) {
+                merge(newOption, option);
+            }
+            else {
+                extend(newOption, option);
+            }
+        }
+
+        // Remove parentModel
+        this.option = newOption;
+        this.parentModel = null;
+    }
+
+    /**
      * If model has option
      */
     isEmpty(): boolean {
@@ -180,7 +210,7 @@ class Model<Opt extends ModelOption = ModelOption> {    // 
TODO: TYPE use unkown
     // Pending
     clone(): Model<Opt> {
         const Ctor = this.constructor;
-        return new (Ctor as any)(zrUtil.clone(this.option));
+        return new (Ctor as any)(clone(this.option));
     }
 
     // setReadOnly(properties): void {
@@ -204,7 +234,7 @@ class Model<Opt extends ModelOption = ModelOption> {    // 
TODO: TYPE use unkown
 
     // FIXME:TS check whether put this method here
     isAnimationEnabled(): boolean {
-        if (!env.node) {
+        if (!env.node && this.option) {
             if (this.option.animation != null) {
                 return !!this.option.animation;
             }
diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts
index eca285f..5d12795 100644
--- a/src/model/mixin/dataFormat.ts
+++ b/src/model/mixin/dataFormat.ts
@@ -86,36 +86,38 @@ class DataFormatMixin {
      * @param dataIndex
      * @param status 'normal' by default
      * @param dataType
-     * @param dimIndex Only used in some chart that
+     * @param labelDimIndex Only used in some chart that
      *        use formatter in different dimensions, like radar.
-     * @param labelProp 'label' by default
-     * @return If not formatter, return null/undefined
+     * @param formatter Formatter given outside.
+     * @return return null/undefined if no formatter
      */
     getFormattedLabel(
         dataIndex: number,
         status?: DisplayState,
         dataType?: string,
-        dimIndex?: number,
-        labelProp?: string
+        labelDimIndex?: number,
+        formatter?: string | ((params: object) => string)
     ): string {
         status = status || 'normal';
         const data = this.getData(dataType);
-        const itemModel = data.getItemModel(dataIndex);
 
         const params = this.getDataParams(dataIndex, dataType);
-        if (dimIndex != null && (params.value instanceof Array)) {
-            params.value = params.value[dimIndex];
+        if (labelDimIndex != null && (params.value instanceof Array)) {
+            params.value = params.value[labelDimIndex];
         }
 
-        // @ts-ignore FIXME:TooltipModel
-        const formatter = itemModel.get(status === 'normal'
-            ? [(labelProp || 'label'), 'formatter']
-            : [status, labelProp || 'label', 'formatter']
-        );
+        if (!formatter) {
+            const itemModel = data.getItemModel(dataIndex);
+            // @ts-ignore
+            formatter = itemModel.get(status === 'normal'
+                ? ['label', 'formatter']
+                : [status, 'label', 'formatter']
+            );
+        }
 
         if (typeof formatter === 'function') {
             params.status = status;
-            params.dimensionIndex = dimIndex;
+            params.dimensionIndex = labelDimIndex;
             return formatter(params);
         }
         else if (typeof formatter === 'string') {
diff --git a/src/stream/Scheduler.ts b/src/stream/Scheduler.ts
index b5759b4..ff156b7 100644
--- a/src/stream/Scheduler.ts
+++ b/src/stream/Scheduler.ts
@@ -106,7 +106,7 @@ class Scheduler {
 
     // Shared with echarts.js, should only be modified by
     // this file and echarts.js
-    unfinished: number;
+    unfinished: boolean;
 
     private _dataProcessorHandlers: StageHandlerInternal[];
     private _visualHandlers: StageHandlerInternal[];
@@ -301,7 +301,7 @@ class Scheduler {
         opt?: PerformStageTaskOpt
     ): void {
         opt = opt || {};
-        let unfinished: number;
+        let unfinished: boolean;
         const scheduler = this;
 
         each(stageHandlers, function (stageHandler, idx) {
@@ -332,7 +332,7 @@ class Scheduler {
                 agentStubMap.each(function (stub) {
                     stub.perform(performArgs);
                 });
-                unfinished |= overallTask.perform(performArgs) as any;
+                unfinished = unfinished || overallTask.perform(performArgs);
             }
             else if (seriesTaskMap) {
                 seriesTaskMap.each(function (task, pipelineId) {
@@ -351,7 +351,7 @@ class Scheduler {
                     performArgs.skip = !stageHandler.performRawSeries
                         && ecModel.isSeriesFiltered(task.context.model);
                     scheduler.updatePayload(task, payload);
-                    unfinished |= task.perform(performArgs) as any;
+                    unfinished = unfinished || task.perform(performArgs);
                 });
             }
         });
@@ -360,18 +360,18 @@ class Scheduler {
             return opt.setDirty && (!opt.dirtyMap || 
opt.dirtyMap.get(task.__pipeline.id));
         }
 
-        this.unfinished |= unfinished;
+        this.unfinished = unfinished || this.unfinished;
     }
 
     performSeriesTasks(ecModel: GlobalModel): void {
-        let unfinished: number;
+        let unfinished: boolean;
 
         ecModel.eachSeries(function (seriesModel) {
             // Progress to the end for dataInit and dataRestore.
-            unfinished |= seriesModel.dataTask.perform() as any;
+            unfinished = seriesModel.dataTask.perform() || unfinished;
         });
 
-        this.unfinished |= unfinished;
+        this.unfinished = unfinished || this.unfinished;
     }
 
     plan(): void {
diff --git a/src/util/LabelManager.ts b/src/util/LabelManager.ts
new file mode 100644
index 0000000..b8b9055
--- /dev/null
+++ b/src/util/LabelManager.ts
@@ -0,0 +1,286 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { OrientedBoundingRect, Text as ZRText, Point, BoundingRect, getECData 
} from './graphic';
+import { MatrixArray } from 'zrender/src/core/matrix';
+import ExtensionAPI from '../ExtensionAPI';
+import {
+    ZRTextAlign,
+    ZRTextVerticalAlign,
+    LabelLayoutOption,
+    LabelLayoutOptionCallback,
+    LabelLayoutOptionCallbackParams
+} from './types';
+import { parsePercent } from './number';
+import SeriesModel from '../model/Series';
+import ChartView from '../view/Chart';
+
+interface DisplayedLabelItem {
+    label: ZRText
+    rect: BoundingRect
+    localRect: BoundingRect
+    obb?: OrientedBoundingRect
+    axisAligned: boolean
+    transform: MatrixArray
+}
+
+interface LabelItem {
+    label: ZRText
+    seriesIndex: number
+    dataIndex: number
+    layoutOption: LabelLayoutOptionCallback | LabelLayoutOption
+}
+
+interface LabelLayoutInnerConfig {
+    overlap: LabelLayoutOption['overlap']
+    overlapMargin: LabelLayoutOption['overlapMargin']
+}
+
+interface SavedLabelAttr {
+    x?: number
+    y?: number
+    rotation?: number
+    align?: ZRTextAlign
+    verticalAlign?: ZRTextVerticalAlign
+    width?: number
+    height?: number
+}
+
+function prepareLayoutCallbackParams(
+    label: ZRText,
+    dataIndex: number,
+    seriesIndex: number
+): LabelLayoutOptionCallbackParams {
+    const host = label.__hostTarget;
+    const labelTransform = label.getComputedTransform();
+    const labelRect = label.getBoundingRect().plain();
+    BoundingRect.applyTransform(labelRect, labelRect, labelTransform);
+    let x = 0;
+    let y = 0;
+    if (labelTransform) {
+        x = labelTransform[4];
+        y = labelTransform[5];
+    }
+
+    let hostRect;
+    if (host) {
+        hostRect = host.getBoundingRect().plain();
+        const transform = host.getComputedTransform();
+        BoundingRect.applyTransform(hostRect, hostRect, transform);
+    }
+
+    return {
+        dataIndex,
+        seriesIndex,
+        text: label.style.text,
+        rect: hostRect,
+        labelRect: labelRect,
+        x, y,
+        align: label.style.align,
+        verticalAlign: label.style.verticalAlign
+    };
+}
+
+const LABEL_OPTION_TO_STYLE_KEYS = ['align', 'verticalAlign', 'width', 
'height'] as const;
+
+class LabelManager {
+
+    private _labelList: LabelItem[] = [];
+    private _labelLayoutConfig: LabelLayoutInnerConfig[] = [];
+
+    // Save default label attributes.
+    // For restore if developers want get back to default value in callback.
+    private _defaultLabelAttr: SavedLabelAttr[] = [];
+
+    constructor() {}
+
+    clearLabels() {
+        this._labelList = [];
+        this._labelLayoutConfig = [];
+    }
+
+    /**
+     * Add label to manager
+     * @param dataIndex
+     * @param seriesIndex
+     * @param label
+     * @param layoutOption
+     */
+    addLabel(dataIndex: number, seriesIndex: number, label: ZRText, 
layoutOption: LabelItem['layoutOption']) {
+        this._labelList.push({
+            seriesIndex,
+            dataIndex,
+            label,
+            layoutOption
+        });
+        // Push an empty config. Will be updated in updateLayoutConfig
+        this._labelLayoutConfig.push({} as LabelLayoutInnerConfig);
+
+        const labelStyle = label.style;
+        this._defaultLabelAttr.push({
+            x: label.x,
+            y: label.y,
+            rotation: label.rotation,
+            align: labelStyle.align,
+            verticalAlign: labelStyle.verticalAlign,
+            width: labelStyle.width,
+            height: labelStyle.height
+        });
+    }
+
+    addLabelsOfSeries(chartView: ChartView) {
+        const seriesModel = chartView.__model;
+        const layoutOption = seriesModel.get('labelLayout');
+        chartView.group.traverse((child) => {
+            if (child.ignore) {
+                return true;    // Stop traverse descendants.
+            }
+
+            // Only support label being hosted on graphic elements.
+            const textEl = child.getTextContent();
+            const dataIndex = getECData(child).dataIndex;
+            if (textEl && dataIndex != null) {
+                this.addLabel(dataIndex, seriesModel.seriesIndex, textEl, 
layoutOption);
+            }
+        });
+    }
+
+    updateLayoutConfig(api: ExtensionAPI) {
+        const width = api.getWidth();
+        const height = api.getHeight();
+        for (let i = 0; i < this._labelList.length; i++) {
+            const labelItem = this._labelList[i];
+            const label = labelItem.label;
+            const hostEl = label.__hostTarget;
+            const layoutConfig = this._labelLayoutConfig[i];
+            const defaultLabelAttr = this._defaultLabelAttr[i];
+            let layoutOption;
+            if (typeof labelItem.layoutOption === 'function') {
+                layoutOption = labelItem.layoutOption(
+                    prepareLayoutCallbackParams(label, labelItem.dataIndex, 
labelItem.seriesIndex)
+                );
+            }
+            else {
+                layoutOption = labelItem.layoutOption;
+            }
+
+            layoutOption = layoutOption || {};
+            // if (hostEl) {
+            //         // Ignore position and rotation config on the host el.
+            //     hostEl.setTextConfig({
+            //         position: null,
+            //         rotation: null
+            //     });
+            // }
+            // label.x = layoutOption.x != null
+            //     ? parsePercent(layoutOption.x, width)
+            //     // Restore to default value if developers don't given a 
value.
+            //     : defaultLabelAttr.x;
+
+            // label.y = layoutOption.y != null
+            //     ? parsePercent(layoutOption.y, height)
+            //     : defaultLabelAttr.y;
+
+            // label.rotation = layoutOption.rotation != null
+            //     ? layoutOption.rotation : defaultLabelAttr.rotation;
+
+            // label.x += layoutOption.dx || 0;
+            // label.y += layoutOption.dy || 0;
+
+            // for (let k = 0; k < LABEL_OPTION_TO_STYLE_KEYS.length; k++) {
+            //     const key = LABEL_OPTION_TO_STYLE_KEYS[k];
+            //     label.setStyle(key, layoutOption[key] != null ? 
layoutOption[key] : defaultLabelAttr[key]);
+            // }
+
+            layoutConfig.overlap = layoutOption.overlap;
+            layoutConfig.overlapMargin = layoutOption.overlapMargin;
+        }
+    }
+
+    layout() {
+        // TODO: sort by priority
+        const labelList = this._labelList;
+
+        const displayedLabels: DisplayedLabelItem[] = [];
+        const mvt = new Point();
+
+        for (let i = 0; i < labelList.length; i++) {
+            const labelItem = labelList[i];
+            const layoutConfig = this._labelLayoutConfig[i];
+            const label = labelItem.label;
+            const transform = label.getComputedTransform();
+            // NOTE: Get bounding rect after getComputedTransform, or label 
may not been updated by the host el.
+            const localRect = label.getBoundingRect();
+            const isAxisAligned = !transform || (transform[1] < 1e-5 && 
transform[2] < 1e-5);
+
+            const globalRect = localRect.clone();
+            globalRect.applyTransform(transform);
+
+            let obb = isAxisAligned ? new OrientedBoundingRect(localRect, 
transform) : null;
+            let overlapped = false;
+            const overlapMargin = layoutConfig.overlapMargin || 0;
+            const marginSqr = overlapMargin * overlapMargin;
+            for (let j = 0; j < displayedLabels.length; j++) {
+                const existsTextCfg = displayedLabels[j];
+                // Fast rejection.
+                if (!globalRect.intersect(existsTextCfg.rect, mvt) && 
mvt.lenSquare() > marginSqr) {
+                    continue;
+                }
+
+                if (isAxisAligned && existsTextCfg.axisAligned) {   // Is 
overlapped
+                    overlapped = true;
+                    break;
+                }
+
+                if (!existsTextCfg.obb) { // If self is not axis aligned. But 
other is.
+                    existsTextCfg.obb = new 
OrientedBoundingRect(existsTextCfg.localRect, existsTextCfg.transform);
+                }
+
+                if (!obb) { // If self is axis aligned. But other is not.
+                    obb = new OrientedBoundingRect(localRect, transform);
+                }
+
+                if (obb.intersect(existsTextCfg.obb, mvt) || mvt.lenSquare() < 
marginSqr) {
+                    overlapped = true;
+                    break;
+                }
+            }
+
+            if (overlapped) {
+                label.hide();
+            }
+            else {
+                label.show();
+                displayedLabels.push({
+                    label,
+                    rect: globalRect,
+                    localRect,
+                    obb,
+                    axisAligned: isAxisAligned,
+                    transform
+                });
+            }
+        }
+    }
+}
+
+
+
+
+export default LabelManager;
\ No newline at end of file
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 9a35173..8020892 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -39,6 +39,8 @@ import CompoundPath from 'zrender/src/graphic/CompoundPath';
 import LinearGradient from 'zrender/src/graphic/LinearGradient';
 import RadialGradient from 'zrender/src/graphic/RadialGradient';
 import BoundingRect from 'zrender/src/core/BoundingRect';
+import OrientedBoundingRect from 'zrender/src/core/OrientedBoundingRect';
+import Point from 'zrender/src/core/Point';
 import IncrementalDisplayable from 
'zrender/src/graphic/IncrementalDisplayable';
 import * as subPixelOptimizeUtil from 
'zrender/src/graphic/helper/subPixelOptimize';
 import { Dictionary } from 'zrender/src/core/types';
@@ -605,19 +607,61 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams {
     ),
     // Fetch text by `opt.labelFetcher.getFormattedLabel(opt.labelDataIndex, 
'normal'/'emphasis', null, opt.labelDimIndex)`
     labelFetcher?: {
-        getFormattedLabel?: (
+        getFormattedLabel: (
             // In MapDraw case it can be string (region name)
             labelDataIndex: LDI,
-            state: DisplayState,
-            dataType: string,
-            labelDimIndex: number
+            status: DisplayState,
+            dataType?: string,
+            labelDimIndex?: number,
+            formatter?: string | ((params: object) => string)
         ) => string
+        // getDataParams: (labelDataIndex: LDI, dataType?: string) => object
     },
     labelDataIndex?: LDI,
     labelDimIndex?: number
 }
 
 
+// function handleSquashCallback<LDI>(
+//     func: Function,
+//     labelDataIndex: LDI,
+//     labelFetcher: SetLabelStyleOpt<LDI>['labelFetcher'],
+//     rect: RectLike,
+//     status: DisplayState
+// ) {
+//     let params: {
+//         status?: DisplayState
+//         rect?: RectLike
+//     };
+//     if (labelFetcher && labelFetcher.getDataParams) {
+//         params = labelFetcher.getDataParams(labelDataIndex);
+//     }
+//     else {
+//         params = {};
+//     }
+//     params.status = status;
+//     params.rect = rect;
+//     return func(params);
+// }
+
+// function getGlobalBoundingRect(el: Element) {
+//     const rect = el.getBoundingRect().clone();
+//     const transform = el.getComputedTransform();
+//     if (transform) {
+//         rect.applyTransform(transform);
+//     }
+//     return rect;
+// }
+
+type LabelModel = Model<LabelOption & {
+    formatter?: string | ((params: any) => string)
+}>;
+type LabelModelForText = Model<Omit<
+    // Remove
+    LabelOption, 'position' | 'rotate'
+> & {
+    formatter?: string | ((params: any) => string)
+}>;
 /**
  * Set normal styles and emphasis styles about text on target element
  * If target is a ZRText. It will create a new style object.
@@ -627,10 +671,14 @@ interface SetLabelStyleOpt<LDI> extends TextCommonParams {
  * NOTICE: Because the style on ZRText will be replaced with new(only x, y are 
keeped).
  * So please use the style on ZRText after use this method.
  */
-export function setLabelStyle<LDI>(
+// eslint-disable-next-line
+function setLabelStyle<LDI>(targetEl: ZRText, normalModel: LabelModelForText, 
emphasisModel: LabelModelForText, opt?: SetLabelStyleOpt<LDI>, 
normalSpecified?: TextStyleProps, emphasisSpecified?: TextStyleProps): void;
+// eslint-disable-next-line
+function setLabelStyle<LDI>(targetEl: Element, normalModel: LabelModel, 
emphasisModel: LabelModel, opt?: SetLabelStyleOpt<LDI>, normalSpecified?: 
TextStyleProps, emphasisSpecified?: TextStyleProps): void;
+function setLabelStyle<LDI>(
     targetEl: Element,
-    normalModel: Model,
-    emphasisModel: Model,
+    normalModel: LabelModel,
+    emphasisModel: LabelModel,
     opt?: SetLabelStyleOpt<LDI>,
     normalSpecified?: TextStyleProps,
     emphasisSpecified?: TextStyleProps
@@ -639,6 +687,31 @@ export function setLabelStyle<LDI>(
     opt = opt || EMPTY_OBJ;
     const isSetOnText = targetEl instanceof ZRText;
 
+    const labelFetcher = opt.labelFetcher;
+    const labelDataIndex = opt.labelDataIndex;
+    const labelDimIndex = opt.labelDimIndex;
+
+    // TODO Performance optimization
+    // normalModel.squash(false, function (func: Function) {
+    //     return handleSquashCallback(
+    //         func,
+    //         labelDataIndex,
+    //         labelFetcher,
+    //         isSetOnText ? null : getGlobalBoundingRect(targetEl),
+    //         'normal'
+    //     );
+    // });
+
+    // emphasisModel.squash(false, function (func: Function) {
+    //     return handleSquashCallback(
+    //         func,
+    //         labelDataIndex,
+    //         labelFetcher,
+    //         isSetOnText ? null : getGlobalBoundingRect(targetEl),
+    //         'emphasis'
+    //     );
+    // });
+
     const showNormal = normalModel.getShallow('show');
     const showEmphasis = emphasisModel.getShallow('show');
 
@@ -647,13 +720,12 @@ export function setLabelStyle<LDI>(
     // label should be displayed, where text is fetched by `normal.formatter` 
or `opt.defaultText`.
     let richText = isSetOnText ? targetEl as ZRText : null;
     if (showNormal || showEmphasis) {
-        const labelFetcher = opt.labelFetcher;
-        const labelDataIndex = opt.labelDataIndex;
-        const labelDimIndex = opt.labelDimIndex;
-
         let baseText;
         if (labelFetcher) {
-            baseText = labelFetcher.getFormattedLabel(labelDataIndex, 
'normal', null, labelDimIndex);
+            baseText = labelFetcher.getFormattedLabel(
+                labelDataIndex, 'normal', null, labelDimIndex,
+                normalModel.get('formatter')
+            );
         }
         if (baseText == null) {
             baseText = isFunction(opt.defaultText) ? 
opt.defaultText(labelDataIndex, opt) : opt.defaultText;
@@ -661,7 +733,10 @@ export function setLabelStyle<LDI>(
         const normalStyleText = baseText;
         const emphasisStyleText = retrieve2(
             labelFetcher
-                ? labelFetcher.getFormattedLabel(labelDataIndex, 'emphasis', 
null, labelDimIndex)
+                ? labelFetcher.getFormattedLabel(
+                    labelDataIndex, 'emphasis', null, labelDimIndex,
+                    emphasisModel.get('formatter')
+                )
                 : null,
             baseText
         );
@@ -738,6 +813,7 @@ export function setLabelStyle<LDI>(
     targetEl.dirty();
 }
 
+export {setLabelStyle};
 /**
  * Set basic textStyle properties.
  */
@@ -802,7 +878,7 @@ export function createTextConfig(
     }
     if (!textStyle.stroke) {
         textConfig.insideStroke = 'auto';
-        // textConfig.outsideStroke = 'auto';
+        textConfig.outsideStroke = 'auto';
     }
     else if (opt.autoColor) {
         // TODO: stroke set to autoColor. if label is inside?
@@ -1080,6 +1156,7 @@ function animateOrSetProps<Props>(
                 delay: animationDelay || 0,
                 easing: animationEasing,
                 done: cb,
+                setToFinal: true,
                 force: !!cb
             })
             : (el.stopAnimation(), el.attr(props), cb && cb());
@@ -1440,5 +1517,7 @@ export {
     LinearGradient,
     RadialGradient,
     BoundingRect,
+    OrientedBoundingRect,
+    Point,
     Path
 };
\ No newline at end of file
diff --git a/src/util/types.ts b/src/util/types.ts
index 0503ec8..47e0277 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -798,6 +798,48 @@ export interface LabelGuideLineOption {
     lineStyle?: LineStyleOption
 }
 
+
+export interface LabelLayoutOptionCallbackParams {
+    dataIndex: number,
+    seriesIndex: number,
+    text: string
+    align: ZRTextAlign
+    verticalAlign: ZRTextVerticalAlign
+    rect: RectLike
+    labelRect: RectLike
+    x: number
+    y: number
+};
+
+export interface LabelLayoutOption {
+    overlap?: 'visible' | 'hidden' | 'blur'
+    /**
+     * Minimal margin between two labels which will be considered as 
overlapped.
+     */
+    overlapMargin?: number
+    /**
+     * Can be absolute px number or percent string.
+     */
+    x?: number | string
+    y?: number | string
+    /**
+     * offset on x based on the original position.
+     */
+    dx?: number
+    /**
+     * offset on y based on the original position.
+     */
+    dy?: number
+    rotation?: number
+    align?: ZRTextAlign
+    verticalAlign?: ZRTextVerticalAlign
+    width?: number
+    height?: number
+}
+
+export type LabelLayoutOptionCallback = (params: 
LabelLayoutOptionCallbackParams) => LabelLayoutOption;
+
+
 interface TooltipFormatterCallback<T> {
     /**
      * For sync callback
@@ -871,7 +913,7 @@ export interface CommonTooltipOption<FormatterParams> {
      *
      * Support to be a callback
      */
-    position?: number[] | string[] | TooltipBuiltinPosition | PositionCallback 
| TooltipBoxLayoutOption
+    position?: (number | string)[] | TooltipBuiltinPosition | PositionCallback 
| TooltipBoxLayoutOption
 
     confine?: boolean
 
@@ -1075,6 +1117,11 @@ export interface SeriesOption extends
      * @default 'column'
      */
     seriesLayoutBy?: 'column' | 'row'
+
+    /**
+     * Global label layout option in label layout stage.
+     */
+    labelLayout?: LabelLayoutOption | LabelLayoutOptionCallback
 }
 
 export interface SeriesOnCartesianOptionMixin {
diff --git a/test/animation-additive.html b/test/animation-additive.html
new file mode 100644
index 0000000..ad62f73
--- /dev/null
+++ b/test/animation-additive.html
@@ -0,0 +1,162 @@
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<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" />
+    </head>
+    <body>
+        <style>
+            h1 {
+                line-height: 60px;
+                height: 60px;
+                background: #146402;
+                text-align: center;
+                font-weight: bold;
+                color: #eee;
+                font-size: 14px;
+            }
+            .chart {
+                height: 800px;
+                width: 50%;
+                float: left;
+            }
+        </style>
+
+        <div id="additive" class="chart"></div>
+        <div id="non-additive" class="chart"></div>
+        <script>
+
+            require(['echarts'], function (echarts) {
+
+                function createChart(domId, additive) {
+
+                    var chart = echarts.init(document.getElementById(domId));
+
+                    chart.setOption({
+                        title: {
+                            text: additive ? 'Additive' : 'Normal',
+                            left: 'center'
+                        },
+                        grid: {
+                            left: '3%',
+                            right: '7%',
+                            bottom: '3%',
+                            containLabel: true
+                        },
+                        legend: {
+                            data: ['女性', '男性'],
+                            left: 'right'
+                        },
+                        xAxis: [
+                            {
+                                type: 'value',
+                                scale: true,
+                                splitNumber: 5,
+                                axisLabel: {
+                                    formatter: '{value} cm'
+                                },
+                                splitLine: {
+                                    show: false
+                                },
+
+                                animationEasingUpdate: 'cubicInOut',
+                                animationDurationUpdate: 1000,
+                                animationAdditive: additive,
+                            }
+                        ],
+                        yAxis: [
+                            {
+                                type: 'value',
+                                scale: true,
+                                splitNumber: 5,
+                                axisLabel: {
+                                    formatter: '{value} kg'
+                                },
+                                splitLine: {
+                                    show: false
+                                },
+
+                                animationEasingUpdate: 'cubicInOut',
+                                animationDurationUpdate: 1000,
+                                animationAdditive: additive,
+                            }
+                        ],
+                        series: [
+                            {
+                                animationEasingUpdate: 'cubicInOut',
+                                animationDurationUpdate: 1000,
+                                animationAdditive: additive,
+                                name: '女性',
+                                type: 'scatter',
+                                data: [[161.2, 51.6], [167.5, 59.0], [159.5, 
49.2], [157.0, 63.0], [155.8, 53.6], [170.0, 59.0], [159.1, 47.6], [166.0, 
69.8], [176.2, 66.8], [160.2, 75.2], [172.5, 55.2], [170.9, 54.2], [172.9, 
62.5], [153.4, 42.0], [160.0, 50.0], [147.2, 49.8], [168.2, 49.2], [175.0, 
73.2], [157.0, 47.8], [167.6, 68.8], [159.5, 50.6], [175.0, 82.5], [166.8, 
57.2], [176.5, 87.8], [170.2, 72.8], [174.0, 54.5], [173.0, 59.8], [179.9, 
67.3], [170.5, 67.8], [160.0, 47.0], [15 [...]
+                                markLine: {
+                                    animationEasingUpdate: 'cubicInOut',
+                                    animationDurationUpdate: 1000,
+                                    animationAdditive: additive,
+                                    lineStyle: {
+                                        type: 'solid'
+                                    },
+                                    data: [
+                                        {type: 'average', name: '平均值'},
+                                        { xAxis: 160 }
+                                    ]
+                                }
+                            },
+                            {
+                                name: '男性',
+                                type: 'scatter',
+                                animationEasingUpdate: 'cubicInOut',
+                                animationDurationUpdate: 1000,
+                                animationAdditive: additive,
+                                data: [[174.0, 65.6], [175.3, 71.8], [193.5, 
80.7], [186.5, 72.6], [187.2, 78.8], [181.5, 74.8], [184.0, 86.4], [184.5, 
78.4], [175.0, 62.0], [184.0, 81.6], [180.0, 76.6], [177.8, 83.6], [192.0, 
90.0], [176.0, 74.6], [174.0, 71.0], [184.0, 79.6], [192.7, 93.8], [171.5, 
70.0], [173.0, 72.4], [176.0, 85.9], [176.0, 78.8], [180.5, 77.8], [172.7, 
66.2], [176.0, 86.4], [173.5, 81.8], [178.0, 89.6], [180.3, 82.8], [180.3, 
76.4], [164.5, 63.2], [173.0, 60.9], [18 [...]
+                                markLine: {
+                                    animationEasingUpdate: 'cubicInOut',
+                                    animationDurationUpdate: 1000,
+                                    animationAdditive: additive,
+                                    lineStyle: {
+                                        type: 'solid'
+                                    },
+                                    data: [
+                                        {type: 'average', name: '平均值'},
+                                        { xAxis: 170 }
+                                    ]
+                                }
+                            }
+                        ]
+                    });
+
+                    return chart;
+                }
+
+                echarts.connect([
+                    createChart('additive', true),
+                    createChart('non-additive', false)
+                ]);
+            });
+        </script>
+    </body>
+</html>
\ No newline at end of file
diff --git a/test/bar-stack.html b/test/bar-stack.html
index 1176c30..2d339bc 100644
--- a/test/bar-stack.html
+++ b/test/bar-stack.html
@@ -46,33 +46,6 @@ under the License.
             ], function (echarts) {
 
                 var option = {
-                    "tooltip": {
-                        "trigger": "axis",
-                        "axisPointer": {
-                            "type": "shadow"
-                        }
-                    },
-                    "toolbox": {
-                        "show": true,
-                        "feature": {
-                            "dataZoom": {
-                                "yAxisIndex": "none"
-                            },
-                            "dataView": {
-                                "readOnly": false
-                            },
-                            "magicType": {
-                                "type": [
-                                    "line",
-                                    "bar",
-                                    "stack",
-                                    "tiled"
-                                ]
-                            },
-                            "restore": {},
-                            "saveAsImage": {}
-                        }
-                    },
                     "xAxis": {
                         type: 'category'
                     },
@@ -99,14 +72,14 @@ under the License.
                         ],
                         barMinHeight: 10,
                         label: {
-                            normal: {show: true}
+                            show: true
                         },
                         "name": "zly13"
                     }, {
                         "type": "bar",
                         "stack": "all",
                         label: {
-                            normal: {show: true}
+                            show: true
                         },
                         "data": [
                             ["哪有那么多审批", 66],
diff --git a/test/graph-label-rotate.html b/test/graph-label-rotate.html
index dec5d01..5b89368 100644
--- a/test/graph-label-rotate.html
+++ b/test/graph-label-rotate.html
@@ -63,13 +63,13 @@ under the License.
                             roam: true,
                             label: {
                                 show: true,
-                                    rotate: 30,
-                                    fontWeight:5,
-                                    fontSize: 26,
-                                    color: "#000",
-                                    distance: 15,
-                                    position: 'inside',
-                                    verticalAlign: 'middle'
+                                rotate: 30,
+                                fontWeight:5,
+                                fontSize: 26,
+                                color: "#000",
+                                distance: 15,
+                                position: 'inside',
+                                verticalAlign: 'middle'
                             },
                             edgeSymbol: ['circle', 'arrow'],
                             edgeSymbolSize: [4, 10],
diff --git a/test/label-overlap.html b/test/label-overlap.html
new file mode 100644
index 0000000..75de7c8
--- /dev/null
+++ b/test/label-overlap.html
@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <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>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <div id="main0"></div>
+        <div id="main1"></div>
+
+
+        <!-- TODO: Tree, Sankey, Map  -->
+        <div id="main2"></div>
+
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+            // $.getJSON('./data/nutrients.json', function (data) {});
+            option = {
+                legend: {
+                    data: ['直接访问', '邮件营销','联盟广告','视频广告','搜索引擎']
+                },
+                grid: {
+                    left: '3%',
+                    right: '4%',
+                    bottom: '3%',
+                    containLabel: true
+                },
+                xAxis:  {
+                    type: 'value'
+                },
+                yAxis: {
+                    type: 'category',
+                    data: ['周一','周二','周三','周四','周五','周六','周日']
+                },
+                series: [
+                    {
+                        name: '直接访问',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            show: true
+                        },
+                        data: [13244, 302, 301, 334, 390, 330, 320]
+                    },
+                    {
+                        name: '邮件营销',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            show: true
+                        },
+                        data: [120, 132, 101, 134, 90, 230, 210]
+                    },
+                    {
+                        name: '联盟广告',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            show: true
+                        },
+                        data: [220, 182, 191, 234, 290, 330, 310]
+                    },
+                    {
+                        name: '视频广告',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            show: true
+                        },
+                        data: [150, 212, 201, 154, 190, 330, 410]
+                    },
+                    {
+                        name: '搜索引擎',
+                        type: 'bar',
+                        stack: '总量',
+                        label: {
+                            show: true
+                        },
+                        data: [820, 832, 901, 934, 1290, 1330, 1320]
+                    }
+                ]
+            }
+            var chart = testHelper.create(echarts, 'main0', {
+                title: [
+                    'Overlap of stacked bar.',
+                    'Case from #6514'
+                ],
+                option: option
+            });
+        });
+        </script>
+
+
+
+        <script>
+            require(['echarts',  'extension/dataTool'], function (echarts, 
dataTool) {
+                $.get('./data/les-miserables.gexf', function (xml) {
+                    var graph = dataTool.gexf.parse(xml);
+                    var categories = [];
+                    for (var i = 0; i < 9; i++) {
+                        categories[i] = {
+                            name: '类目' + i
+                        };
+                    }
+                    graph.nodes.forEach(function (node) {
+                        delete node.itemStyle;
+                        node.value = node.symbolSize;
+                        node.category = node.attributes['modularity_class'];
+                    });
+                    graph.links.forEach(function (link) {
+                        delete link.lineStyle;
+                    });
+                    var option = {
+                        legend: [{}],
+                        animationDurationUpdate: 1500,
+                        animationEasingUpdate: 'quinticInOut',
+
+                        series : [
+                            {
+                                name: 'Les Miserables',
+                                type: 'graph',
+                                layout: 'none',
+                                data: graph.nodes,
+                                links: graph.links,
+                                categories: categories,
+                                roam: true,
+                                draggable: true,
+
+                                label: {
+                                    show: true,
+                                    formatter: '{b}',
+                                    position: 'right'
+                                },
+
+                                // labelLayout: function (params) {
+                                //     return {
+                                //         show: params.rect.width > 10,
+                                //         overlap: 'hidden'
+                                //     }
+                                // },
+                                emphasis: {
+                                    label: {
+                                        show: true
+                                    }
+                                },
+                                lineStyle: {
+                                    color: 'source',
+                                    curveness: 0.3
+                                },
+                                emphasis: {
+                                    lineStyle: {
+                                        width: 10
+                                    }
+                                }
+                            }
+                        ]
+                    };
+
+                    var chart = testHelper.create(echarts, 'main1', {
+                        title: [
+                            'Hide overlap in graph zooming.'
+                        ],
+                        height: 800,
+                        option: option
+                    });
+                });
+            });
+            </script>
+
+    </body>
+</html>
+


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to