You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/04/22 14:53:13 UTC

[incubator-echarts] branch label-enhancement created (now 5809720)

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

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


      at 5809720  feat: add label manager for each series to layout label.

This branch includes the following new commits:

     new 5809720  feat: add label manager for each series to layout label.

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



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


[incubator-echarts] 01/01: feat: add label manager for each series to layout label.

Posted by sh...@apache.org.
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 <bm...@gmail.com>
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: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org