You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2020/09/07 05:27:40 UTC

[incubator-echarts] branch new-tooltip2 created (now bb4559f)

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

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


      at bb4559f  fix: (1) Fix some buggy display result in tooltip, like 2 value axis scatter, add add test cases for them. (2) Buggy results in tooltip.renderModel: 'richText' for each cases. And add test cases for them. (3) Add styles to renderMode: 'richText', make them the same as renderMode: 'html'. (4) Make `tooltip.confine` default `true` if `tooltip.renderMode` is `richText`. And fix the positioning of it to avoid to overflow the container, which will be cut. (5) Fix the `tooltip [...]

This branch includes the following new commits:

     new bb4559f  fix: (1) Fix some buggy display result in tooltip, like 2 value axis scatter, add add test cases for them. (2) Buggy results in tooltip.renderModel: 'richText' for each cases. And add test cases for them. (3) Add styles to renderMode: 'richText', make them the same as renderMode: 'html'. (4) Make `tooltip.confine` default `true` if `tooltip.renderMode` is `richText`. And fix the positioning of it to avoid to overflow the container, which will be cut. (5) Fix the `tooltip [...]

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: fix: (1) Fix some buggy display result in tooltip, like 2 value axis scatter, add add test cases for them. (2) Buggy results in tooltip.renderModel: 'richText' for each cases. And add test cases for them. (3) Add styles to renderMode: 'richText', make them the same as renderMode: 'html'. (4) Make `tooltip.confine` default `true` if `tooltip.renderMode` is `richText`. And fix the positioning of it to avoid to overflow the container, which will be cut. (5) Fix the `tooltip.formatter` callbac [...]

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

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

commit bb4559fe4c6be0cc6cd16d445ec34f99d8503143
Author: 100pah <su...@gmail.com>
AuthorDate: Mon Sep 7 13:24:43 2020 +0800

    fix:
    (1) Fix some buggy display result in tooltip, like 2 value axis scatter, add add test cases for them.
    (2) Buggy results in tooltip.renderModel: 'richText' for each cases. And add test cases for them.
    (3) Add styles to renderMode: 'richText', make them the same as renderMode: 'html'.
    (4) Make `tooltip.confine` default `true` if `tooltip.renderMode` is `richText`. And fix the positioning of it to avoid to overflow the container, which will be cut.
    (5) Fix the `tooltip.formatter` callback in `renderMode: 'richText'`, previously the concatenating rich text by `params.marker.content` does not work.
    (6) Refactor the implementation of `Series['formatTooltip']`:
    Add a abstract layer named `TooltipMarkup` to describe the tooltip layout requirement for each series,
    instead of lots of messy code to concatenate `html` and `richText` in each series.
    `TooltipMarkup` is responsible for generating `html` or `richText` finally.
    Add `TooltipMarkup` is also in charge of sort the tooltip by `valueAsc`, `valueDesc`, `seriesAsc`, `seriesDesc`.
    
    [TEST_CASES]: test/new-tooltip.html
    [PENDING]: Do we need to expose API (like `tooltipMarkup.ts#TooltipMarkupStyleCreator`) to `tooltip.formatter` callback, by which users can add richText style themselves. At present users is not able to do that.
    But if we expose that kind of API, there is an issue:
    should we use ZRender style (use `fill` to describe text color)
    or ECharts label style (use `color` to describe text color).
---
 src/chart/graph/GraphSeries.ts                     |  38 +-
 src/chart/lines/LinesSeries.ts                     |  18 +-
 src/chart/map/MapSeries.ts                         |  30 +-
 src/chart/radar/RadarSeries.ts                     |  67 +-
 src/chart/sankey/SankeySeries.ts                   |  43 +-
 src/chart/themeRiver/ThemeRiverSeries.ts           |  28 +-
 src/chart/tree/TreeSeries.ts                       |  25 +-
 src/chart/treemap/TreemapSeries.ts                 |  19 +-
 src/component/axisPointer/axisTrigger.ts           |   4 +-
 src/component/marker/MarkerModel.ts                |  37 +-
 src/component/timeline/SliderTimelineModel.ts      |   2 +-
 src/component/timeline/SliderTimelineView.ts       |   5 +-
 src/component/tooltip/TooltipHTMLContent.ts        |  49 +-
 src/component/tooltip/TooltipModel.ts              |  10 +-
 src/component/tooltip/TooltipRichContent.ts        |  76 +-
 src/component/tooltip/TooltipView.ts               | 301 ++++----
 .../tooltip/helper.ts}                             |  32 +-
 src/component/tooltip/seriesFormatTooltip.ts       | 154 ++++
 src/component/tooltip/tooltipMarkup.ts             | 529 +++++++++++++
 src/coord/radar/IndicatorAxis.ts                   |   2 -
 src/model/Series.ts                                | 191 +----
 src/model/mixin/dataFormat.ts                      |  83 ++-
 src/util/format.ts                                 | 121 ++-
 src/util/log.ts                                    |   2 +-
 src/util/model.ts                                  |   5 +-
 src/util/number.ts                                 |   9 +
 src/util/time.ts                                   |   2 +-
 src/util/types.ts                                  |   8 +-
 test/new-tooltip.html                              | 825 ++++++++++++++-------
 29 files changed, 1765 insertions(+), 950 deletions(-)

diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts
index 9cac9cf..1623c9c 100644
--- a/src/chart/graph/GraphSeries.ts
+++ b/src/chart/graph/GraphSeries.ts
@@ -21,7 +21,6 @@ import List from '../../data/List';
 import * as zrUtil from 'zrender/src/core/util';
 import {defaultEmphasis} from '../../util/model';
 import Model from '../../model/Model';
-import {encodeHTML} from '../../util/format';
 import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge';
 import LegendVisualProvider from '../../visual/LegendVisualProvider';
 import {
@@ -43,8 +42,7 @@ import {
     LineLabelOption,
     StatesOptionMixin,
     GraphEdgeItemObject,
-    OptionDataValueNumeric,
-    TooltipRenderMode
+    OptionDataValueNumeric
 } from '../../util/types';
 import SeriesModel from '../../model/Series';
 import Graph from '../../data/Graph';
@@ -52,6 +50,8 @@ import GlobalModel from '../../model/Global';
 import { VectorArray } from 'zrender/src/core/vector';
 import { ForceLayoutInstance } from './forceLayout';
 import { LineDataVisual } from '../../visual/commonVisualTypes';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
+import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip';
 
 type GraphDataValue = OptionDataValue | OptionDataValue[];
 
@@ -333,14 +333,10 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
         return this._categoriesData;
     }
 
-    /**
-     * @override
-     */
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
+        dataType: string
     ) {
         if (dataType === 'edge') {
             const nodeData = this.getData();
@@ -349,19 +345,23 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
             const sourceName = nodeData.getName(edge.node1.dataIndex);
             const targetName = nodeData.getName(edge.node2.dataIndex);
 
-            const html = [];
-            sourceName != null && html.push(sourceName);
-            targetName != null && html.push(targetName);
-            let htmlStr = encodeHTML(html.join(' > '));
+            const nameArr = [];
+            sourceName != null && nameArr.push(sourceName);
+            targetName != null && nameArr.push(targetName);
 
-            if (params.value) {
-                htmlStr += ' : ' + encodeHTML(params.value);
-            }
-            return htmlStr;
-        }
-        else { // dataType === 'node' or empty
-            return super.formatTooltip.apply(this, arguments as any);
+            return createTooltipMarkup('nameValue', {
+                name: nameArr.join(' > '),
+                value: params.value,
+                noValue: params.value == null
+            });
         }
+        // dataType === 'node' or empty
+        const nodeMarkup = defaultSeriesFormatTooltip({
+            series: this,
+            dataIndex: dataIndex,
+            multipleSeries: multipleSeries
+        });
+        return nodeMarkup;
     }
 
     _updateCategoriesData() {
diff --git a/src/chart/lines/LinesSeries.ts b/src/chart/lines/LinesSeries.ts
index 905a49c..a75d7e4 100644
--- a/src/chart/lines/LinesSeries.ts
+++ b/src/chart/lines/LinesSeries.ts
@@ -22,7 +22,6 @@
 import SeriesModel from '../../model/Series';
 import List from '../../data/List';
 import { concatArray, mergeAll, map } from 'zrender/src/core/util';
-import {encodeHTML} from '../../util/format';
 import CoordinateSystem from '../../CoordinateSystem';
 import {
     SeriesOption,
@@ -34,11 +33,11 @@ import {
     LineStyleOption,
     OptionDataValue,
     LineLabelOption,
-    StatesOptionMixin,
-    TooltipRenderMode
+    StatesOptionMixin
 } from '../../util/types';
 import GlobalModel from '../../model/Global';
 import type { LineDrawModelOption } from '../helper/LineDraw';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
 
 const Uint32Arr = typeof Uint32Array === 'undefined' ? Array : Uint32Array;
 const Float64Arr = typeof Float64Array === 'undefined' ? Array : Float64Array;
@@ -324,8 +323,7 @@ class LinesSeriesModel extends SeriesModel<LinesSeriesOption> {
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
+        dataType: string
     ) {
         const data = this.getData();
         const itemModel = data.getItemModel<LinesDataItemOption>(dataIndex);
@@ -335,11 +333,13 @@ class LinesSeriesModel extends SeriesModel<LinesSeriesOption> {
         }
         const fromName = itemModel.get('fromName');
         const toName = itemModel.get('toName');
-        const html = [];
-        fromName != null && html.push(fromName);
-        toName != null && html.push(toName);
+        const nameArr = [];
+        fromName != null && nameArr.push(fromName);
+        toName != null && nameArr.push(toName);
 
-        return encodeHTML(html.join(' > '));
+        return createTooltipMarkup('nameValue', {
+            name: nameArr.join(' > ')
+        });
     }
 
     preventIncremental() {
diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts
index 98d4c66..0354b4d 100644
--- a/src/chart/map/MapSeries.ts
+++ b/src/chart/map/MapSeries.ts
@@ -21,7 +21,6 @@
 import * as zrUtil from 'zrender/src/core/util';
 import createListSimply from '../helper/createListSimply';
 import SeriesModel from '../../model/Series';
-import {encodeHTML, addCommas, concatTooltipHtml} from '../../util/format';
 import geoSourceManager from '../../coord/geo/geoSourceManager';
 import {makeSeriesEncodeForNameBased} from '../../data/helper/sourceHelper';
 import {
@@ -34,14 +33,14 @@ import {
     OptionDataValueNumeric,
     ParsedValue,
     SeriesOnGeoOptionMixin,
-    StatesOptionMixin,
-    TooltipRenderMode
+    StatesOptionMixin
 } from '../../util/types';
 import { Dictionary } from 'zrender/src/core/types';
 import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel';
 import List from '../../data/List';
 import Model from '../../model/Model';
 import Geo from '../../coord/geo/Geo';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
 
 export interface MapStateOption {
     itemStyle?: GeoItemStyleOption
@@ -185,12 +184,11 @@ class MapSeries extends SeriesModel<MapSeriesOption> {
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
-    ): string {
+        dataType: string
+    ) {
         // FIXME orignalData and data is a bit confusing
         const data = this.getData();
-        const formattedValue = addCommas(this.getRawValue(dataIndex));
+        const value = this.getRawValue(dataIndex);
         const name = data.getName(dataIndex);
 
         const seriesGroup = this.seriesGroup;
@@ -199,19 +197,17 @@ class MapSeries extends SeriesModel<MapSeriesOption> {
             const otherIndex = seriesGroup[i].originalData.indexOfName(name);
             const valueDim = data.mapDimension('value');
             if (!isNaN(seriesGroup[i].originalData.get(valueDim, otherIndex) as number)) {
-                seriesNames.push(
-                    encodeHTML(seriesGroup[i].name)
-                );
+                seriesNames.push(seriesGroup[i].name);
             }
         }
 
-        if (renderMode === 'richText') {
-            return seriesNames.join(', ') + (seriesNames.length ? '\n' : '')
-                + encodeHTML(name) + ': ' + formattedValue;
-        }
-
-        return `<div style="font-size:12px;color:#6e7079;">${seriesNames.join(', ')}</div>`
-            + concatTooltipHtml(name, formattedValue);
+        return createTooltipMarkup('section', {
+            header: seriesNames.join(', '),
+            noHeader: !seriesNames.length,
+            blocks: [createTooltipMarkup('nameValue', {
+                name: name, value: value
+            })]
+        });
     }
 
     getTooltipPosition = function (this: MapSeries, dataIndex: number): number[] {
diff --git a/src/chart/radar/RadarSeries.ts b/src/chart/radar/RadarSeries.ts
index 4c99034..f7c5175 100644
--- a/src/chart/radar/RadarSeries.ts
+++ b/src/chart/radar/RadarSeries.ts
@@ -20,7 +20,6 @@
 import SeriesModel from '../../model/Series';
 import createListSimply from '../helper/createListSimply';
 import * as zrUtil from 'zrender/src/core/util';
-import {encodeHTML, concatTooltipHtml} from '../../util/format';
 import LegendVisualProvider from '../../visual/LegendVisualProvider';
 import {
     SeriesOption,
@@ -31,13 +30,12 @@ import {
     AreaStyleOption,
     OptionDataValue,
     StatesOptionMixin,
-    OptionDataItemObject,
-    TooltipRenderMode,
-    TooltipOrderMode
+    OptionDataItemObject
 } from '../../util/types';
 import GlobalModel from '../../model/Global';
 import List from '../../data/List';
 import Radar from '../../coord/radar/Radar';
+import { createTooltipMarkup, retrieveVisualColorForTooltipMarker as retrieveVisualColorForTooltip } from '../../component/tooltip/tooltipMarkup';
 
 type RadarSeriesDataValue = OptionDataValue[];
 
@@ -98,60 +96,31 @@ class RadarSeriesModel extends SeriesModel<RadarSeriesOption> {
     formatTooltip(
         dataIndex: number,
         multipleSeries?: boolean,
-        dataType?: string,
-        renderMode?: TooltipRenderMode,
-        order?: TooltipOrderMode
+        dataType?: string
     ) {
         const data = this.getData();
         const coordSys = this.coordinateSystem;
         const indicatorAxes = coordSys.getIndicatorAxes();
         const name = this.getData().getName(dataIndex);
-        zrUtil.each(indicatorAxes, function (axis, idx) {
-            axis.value = data.get(data.mapDimension(axis.dim), dataIndex);
-        });
-        switch (order) {
-            case 'valueAsc':
-                indicatorAxes.sort(function (a, b) {
-                    return +(a.value) - +(b.value);
-                });
-                break;
-
-            case 'valueDesc':
-                indicatorAxes.sort(function (a, b) {
-                    return +(b.value) - +(a.value);
-                });
-                break;
-
-            case 'seriesDesc':
-                indicatorAxes.reverse();
-                break;
+        const nameToDisplay = name === '' ? this.name : name;
+        const markerColor = retrieveVisualColorForTooltip(this, dataIndex);
 
-            case 'seriesAsc':
-            default:
-                break;
-        }
-
-        if (renderMode === 'richText') {
-            return encodeHTML(name === '' ? this.name : name) + '\n'
-                + zrUtil.map(indicatorAxes, function (axis) {
-                    const val = data.get(data.mapDimension(axis.dim), dataIndex);
-                    return encodeHTML(axis.name) + ': ' + val;
-                }).join('\n');
-        }
-        return '<div style="font-size:12px;color:#6e7079;line-height:1;margin-top:-4px;">'
-            + encodeHTML(name === '' ? this.name : name)
-            + '</div>'
-            + zrUtil.map(indicatorAxes, function (axis) {
+        return createTooltipMarkup('section', {
+            header: nameToDisplay,
+            sortBlocks: true,
+            blocks: zrUtil.map(indicatorAxes, axis => {
                 const val = data.get(data.mapDimension(axis.dim), dataIndex);
-                return '<div style="margin: 11px 0 0;line-height:1">'
-                    + concatTooltipHtml(axis.name, val)
-                    + '</div>';
-            }).join('');
+                return createTooltipMarkup('nameValue', {
+                    markerType: 'subItem',
+                    markerColor: markerColor,
+                    name: axis.name,
+                    value: val,
+                    sortParam: val
+                });
+            })
+        });
     }
 
-    /**
-     * @implement
-     */
     getTooltipPosition(dataIndex: number) {
         if (dataIndex != null) {
             const data = this.getData();
diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts
index 08428f2..28d5e6f 100644
--- a/src/chart/sankey/SankeySeries.ts
+++ b/src/chart/sankey/SankeySeries.ts
@@ -19,7 +19,6 @@
 
 import SeriesModel from '../../model/Series';
 import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge';
-import {concatTooltipHtml, encodeHTML} from '../../util/format';
 import Model from '../../model/Model';
 import {
     SeriesOption,
@@ -33,12 +32,13 @@ import {
     StatesOptionMixin,
     OptionDataItemObject,
     GraphEdgeItemObject,
-    OptionDataValueNumeric,
-    TooltipRenderMode
+    OptionDataValueNumeric
 } from '../../util/types';
 import GlobalModel from '../../model/Global';
 import List from '../../data/List';
 import { LayoutRect } from '../../util/layout';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
+import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip';
 
 type FocusNodeAdjacency = boolean | 'inEdges' | 'outEdges' | 'allEdges';
 
@@ -225,38 +225,37 @@ class SankeySeriesModel extends SeriesModel<SankeySeriesOption> {
         return this.getGraph().edgeData;
     }
 
-    /**
-     * @override
-     */
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: 'node' | 'edge',
-        renderMode: TooltipRenderMode
+        dataType: 'node' | 'edge'
     ) {
+        function noValue(val: unknown): boolean {
+            return isNaN(val as number) || val == null;
+        }
         // dataType === 'node' or empty do not show tooltip by default
         if (dataType === 'edge') {
             const params = this.getDataParams(dataIndex, dataType);
             const rawDataOpt = params.data;
-            if (renderMode === 'richText') {
-                return encodeHTML(rawDataOpt.source + ' -- ' + rawDataOpt.target) + params.value;
-            }
-            return '<div style="line-height:1">'
-                + concatTooltipHtml(rawDataOpt.source + ' -- ' + rawDataOpt.target, params.value || '')
-                + '</div>';
+            const edgeValue = params.value;
+            const edgeName = rawDataOpt.source + ' -- ' + rawDataOpt.target;
+            return createTooltipMarkup('nameValue', {
+                name: edgeName,
+                value: edgeValue,
+                noValue: noValue(edgeValue)
+            });
         }
-        else if (dataType === 'node') {
+        // dataType === 'node'
+        else {
             const node = this.getGraph().getNodeByIndex(dataIndex);
             const value = node.getLayout().value;
             const name = this.getDataParams(dataIndex, dataType).data.name;
-            if (renderMode === 'richText') {
-                return encodeHTML(value ? name : '') + ': ' + (value || '');
-            }
-            return '<div style="line-height:1">'
-                + concatTooltipHtml(value ? name : '', value || '')
-                + '</div>';
+            return createTooltipMarkup('nameValue', {
+                name: name,
+                value: value,
+                noValue: noValue(value)
+            });
         }
-        return super.formatTooltip(dataIndex, multipleSeries, dataType, renderMode);
     }
 
     optionUpdated() {
diff --git a/src/chart/themeRiver/ThemeRiverSeries.ts b/src/chart/themeRiver/ThemeRiverSeries.ts
index 3b97a25..d1650e5 100644
--- a/src/chart/themeRiver/ThemeRiverSeries.ts
+++ b/src/chart/themeRiver/ThemeRiverSeries.ts
@@ -23,7 +23,6 @@ import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper';
 import List from '../../data/List';
 import * as zrUtil from 'zrender/src/core/util';
 import {groupData, SINGLE_REFERRING} from '../../util/model';
-import {concatTooltipHtml, encodeHTML} from '../../util/format';
 import LegendVisualProvider from '../../visual/LegendVisualProvider';
 import {
     SeriesOption,
@@ -33,12 +32,12 @@ import {
     OptionDataValueNumeric,
     ItemStyleOption,
     BoxLayoutOptionMixin,
-    ZRColor,
-    TooltipRenderMode
+    ZRColor
 } from '../../util/types';
 import SingleAxis from '../../coord/single/SingleAxis';
 import GlobalModel from '../../model/Global';
 import Single from '../../coord/single/Single';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
 
 const DATA_NAME_INDEX = 2;
 
@@ -285,29 +284,16 @@ class ThemeRiverSeriesModel extends SeriesModel<ThemeRiverSeriesOption> {
         return {dataIndices: indices, nestestValue: nestestValue};
     }
 
-    /**
-     * @override
-     * @param {number} dataIndex  index of data
-     */
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
-    ): string {
+        dataType: string
+    ) {
         const data = this.getData();
-        const htmlName = data.getName(dataIndex);
-        let htmlValue = data.get(data.mapDimension('value'), dataIndex);
-        if (isNaN(htmlValue as number) || htmlValue == null) {
-            htmlValue = '-';
-        }
+        const name = data.getName(dataIndex);
+        const value = data.get(data.mapDimension('value'), dataIndex);
 
-        if (renderMode === 'richText') {
-            return encodeHTML(htmlName) + ': ' + htmlValue;
-        }
-        return '<div style="margin: 11px 0 0;line-height:1">'
-            + concatTooltipHtml(htmlName, htmlValue)
-            + '</div>';
+        return createTooltipMarkup('nameValue', { name: name, value: value });
     }
 
     static defaultOption: ThemeRiverSeriesOption = {
diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts
index 28f5046..e5a49f6 100644
--- a/src/chart/tree/TreeSeries.ts
+++ b/src/chart/tree/TreeSeries.ts
@@ -19,7 +19,6 @@
 
 import SeriesModel from '../../model/Series';
 import Tree from '../../data/Tree';
-import { concatTooltipHtml, encodeHTML } from '../../util/format';
 import {
     SeriesOption,
     SymbolOptionMixin,
@@ -30,13 +29,13 @@ import {
     LabelOption,
     OptionDataValue,
     StatesOptionMixin,
-    OptionDataItemObject,
-    TooltipRenderMode
+    OptionDataItemObject
 } from '../../util/types';
 import List from '../../data/List';
 import View from '../../coord/View';
 import { LayoutRect } from '../../util/layout';
 import Model from '../../model/Model';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
 
 interface CurveLineStyleOption extends LineStyleOption{
     curveness?: number
@@ -201,15 +200,11 @@ class TreeSeriesModel extends SeriesModel<TreeSeriesOption> {
         this.option.center = center;
     }
 
-    /**
-     * @override
-     */
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
-    ): string {
+        dataType: string
+    ) {
         const tree = this.getData().tree;
         const realRoot = tree.root.children[0];
         let node = tree.getNodeByDataIndex(dataIndex);
@@ -220,13 +215,11 @@ class TreeSeriesModel extends SeriesModel<TreeSeriesOption> {
             node = node.parentNode;
         }
 
-        if (renderMode === 'richText') {
-            return encodeHTML(name) + ': ' + ((isNaN(value as number) || value == null) ? '' : value);
-        }
-
-        return '<div style="line-height:1">'
-            + concatTooltipHtml(name, (isNaN(value as number) || value == null) ? '' : value)
-            + '</div>';
+        return createTooltipMarkup('nameValue', {
+            name: name,
+            value: value,
+            noValue: isNaN(value as number) || value == null
+        });
     }
 
     static defaultOption: TreeSeriesOption = {
diff --git a/src/chart/treemap/TreemapSeries.ts b/src/chart/treemap/TreemapSeries.ts
index c622a74..5444a26 100644
--- a/src/chart/treemap/TreemapSeries.ts
+++ b/src/chart/treemap/TreemapSeries.ts
@@ -21,11 +21,6 @@ import * as zrUtil from 'zrender/src/core/util';
 import SeriesModel from '../../model/Series';
 import Tree, { TreeNode } from '../../data/Tree';
 import Model from '../../model/Model';
-import {
-    addCommas,
-    concatTooltipHtml,
-    encodeHTML
-} from '../../util/format';
 import {wrapTreePathInfo} from '../helper/treeHelper';
 import {
     SeriesOption,
@@ -33,7 +28,6 @@ import {
     ItemStyleOption,
     LabelOption,
     RoamOptionMixin,
-    TooltipRenderMode,
     CallbackDataParams,
     ColorString,
     StatesOptionMixin
@@ -42,6 +36,7 @@ import GlobalModel from '../../model/Global';
 import { LayoutRect } from '../../util/layout';
 import List from '../../data/List';
 import { normalizeToArray } from '../../util/model';
+import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
 
 // Only support numberic value.
 type TreemapSeriesDataValue = number | number[];
@@ -377,21 +372,13 @@ class TreemapSeriesModel extends SeriesModel<TreemapSeriesOption> {
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
+        dataType: string
     ) {
         const data = this.getData();
         const value = this.getRawValue(dataIndex) as TreemapSeriesDataValue;
-        const formattedValue = zrUtil.isArray(value)
-            ? addCommas(value[0] as number) : addCommas(value as number);
         const name = data.getName(dataIndex);
 
-        if (renderMode === 'richText') {
-            return encodeHTML(name) + ': ' + formattedValue;
-        }
-        return '<div style="line-height:1">'
-            + concatTooltipHtml(name, formattedValue)
-            + '</div>';
+        return createTooltipMarkup('nameValue', { name: name, value: value });
     }
 
     /**
diff --git a/src/component/axisPointer/axisTrigger.ts b/src/component/axisPointer/axisTrigger.ts
index f9fedc9..0bc672f 100644
--- a/src/component/axisPointer/axisTrigger.ts
+++ b/src/component/axisPointer/axisTrigger.ts
@@ -41,7 +41,7 @@ interface DataIndex {
 
 type BatchItem = DataIndex;
 
-interface DataByAxis {
+export interface DataByAxis {
     // TODO: TYPE Value type
     value: string | number
     axisIndex: number
@@ -56,7 +56,7 @@ interface DataByAxis {
         formatter: AxisPointerOption['label']['formatter']
     }
 }
-interface DataByCoordSys {
+export interface DataByCoordSys {
     coordSysId: string
     coordSysIndex: number
     coordSysType: string
diff --git a/src/component/marker/MarkerModel.ts b/src/component/marker/MarkerModel.ts
index 243d767..341bf5b 100644
--- a/src/component/marker/MarkerModel.ts
+++ b/src/component/marker/MarkerModel.ts
@@ -19,8 +19,7 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
-import {addCommas, concatTooltipHtml, encodeHTML} from '../../util/format';
-import DataFormatMixin from '../../model/mixin/dataFormat';
+import { DataFormatMixin } from '../../model/mixin/dataFormat';
 import ComponentModel from '../../model/Component';
 import SeriesModel from '../../model/Series';
 import {
@@ -29,13 +28,13 @@ import {
     AnimationOptionMixin,
     Dictionary,
     CommonTooltipOption,
-    ScaleDataValue,
-    TooltipRenderMode
+    ScaleDataValue
 } from '../../util/types';
 import Model from '../../model/Model';
 import GlobalModel from '../../model/Global';
 import List from '../../data/List';
 import { makeInner, defaultEmphasis } from '../../util/model';
+import { createTooltipMarkup } from '../tooltip/tooltipMarkup';
 
 function fillLabel(opt: DisplayStateHostOption) {
     defaultEmphasis(opt, 'label', ['show']);
@@ -201,27 +200,21 @@ abstract class MarkerModel<Opts extends MarkerOption = MarkerOption> extends Com
     formatTooltip(
         dataIndex: number,
         multipleSeries: boolean,
-        dataType: string,
-        renderMode: TooltipRenderMode
+        dataType: string
     ) {
         const data = this.getData();
         const value = this.getRawValue(dataIndex);
-        const formattedValue = zrUtil.isArray(value)
-            ? zrUtil.map(value, addCommas).join(', ') : addCommas(value as number);
-        const name = encodeHTML(data.getName(dataIndex));
-        let html = `<div style="font-size:12px;line-height:1;margin:0 0 8px 0;">${encodeHTML(this.name)}</div>`;
-        if (value != null || name) {
-            html += renderMode === 'html' ? '' : '\n';
-        }
-        if (name) {
-            html += `<div style="line-height:1"><span style="font-size:12px;color:#6e7079;">${name}</span>`;
-        }
-        if (value != null) {
-            html = renderMode === 'html'
-                ? concatTooltipHtml(html, formattedValue, true) + (name ? '</div>' : '')
-                : (html + formattedValue);
-        }
-        return html;
+        const itemName = data.getName(dataIndex);
+
+        return createTooltipMarkup('section', {
+            header: this.name,
+            blocks: [createTooltipMarkup('nameValue', {
+                name: itemName,
+                value: value,
+                noName: !itemName,
+                noValue: value == null
+            })]
+        });
     }
 
     getData(): List<this> {
diff --git a/src/component/timeline/SliderTimelineModel.ts b/src/component/timeline/SliderTimelineModel.ts
index 72a96ae..a5cc4e4 100644
--- a/src/component/timeline/SliderTimelineModel.ts
+++ b/src/component/timeline/SliderTimelineModel.ts
@@ -18,7 +18,7 @@
 */
 
 import TimelineModel, { TimelineOption } from './TimelineModel';
-import DataFormatMixin from '../../model/mixin/dataFormat';
+import { DataFormatMixin } from '../../model/mixin/dataFormat';
 import ComponentModel from '../../model/Component';
 import { mixin } from 'zrender/src/core/util';
 import List from '../../data/List';
diff --git a/src/component/timeline/SliderTimelineView.ts b/src/component/timeline/SliderTimelineView.ts
index a088e7f..e8688f8 100644
--- a/src/component/timeline/SliderTimelineView.ts
+++ b/src/component/timeline/SliderTimelineView.ts
@@ -26,7 +26,6 @@ import TimelineView from './TimelineView';
 import TimelineAxis from './TimelineAxis';
 import {createSymbol} from '../../util/symbol';
 import * as numberUtil from '../../util/number';
-import {encodeHTML} from '../../util/format';
 import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import { merge, each, extend, clone, isString, bind, defaults, retrieve2 } from 'zrender/src/core/util';
@@ -46,6 +45,7 @@ import { parsePercent } from 'zrender/src/contain/text';
 import { makeInner } from '../../util/model';
 import { getECData } from '../../util/ecData';
 import { enableHoverEmphasis } from '../../util/states';
+import { createTooltipMarkup } from '../tooltip/tooltipMarkup';
 
 const PI = Math.PI;
 
@@ -129,7 +129,8 @@ class SliderTimelineView extends TimelineView {
             const axis = this._axis = this._createAxis(layoutInfo, timelineModel);
 
             timelineModel.formatTooltip = function (dataIndex: number) {
-                return encodeHTML(axis.scale.getLabel({value: dataIndex}));
+                const name = axis.scale.getLabel({value: dataIndex});
+                return createTooltipMarkup('nameValue', { noName: true, value: name });
             };
 
             each(
diff --git a/src/component/tooltip/TooltipHTMLContent.ts b/src/component/tooltip/TooltipHTMLContent.ts
index 8cdf33f..2545342 100644
--- a/src/component/tooltip/TooltipHTMLContent.ts
+++ b/src/component/tooltip/TooltipHTMLContent.ts
@@ -27,10 +27,12 @@ import ExtensionAPI from '../../ExtensionAPI';
 import { ZRenderType } from 'zrender/src/zrender';
 import { TooltipOption } from './TooltipModel';
 import Model from '../../model/Model';
-import { ZRRawEvent, Dictionary } from 'zrender/src/core/types';
+import { ZRRawEvent } from 'zrender/src/core/types';
 import { ColorString, ZRColor } from '../../util/types';
 import CanvasPainter from 'zrender/src/canvas/Painter';
 import SVGPainter from 'zrender/src/svg/Painter';
+import { shouldTooltipConfine } from './helper';
+import { getPaddingFromTooltipModel } from './tooltipMarkup';
 
 const each = zrUtil.each;
 const toCamelCase = formatUtil.toCamelCase;
@@ -50,22 +52,6 @@ function mirrowPos(pos: string): string {
     return pos;
 }
 
-
-function getFinalColor(color: ZRColor): string {
-    let finalNearPointColor = '#fff';
-    if (zrUtil.isObject(color) && color.type !== 'pattern') {
-        finalNearPointColor = color.colorStops[0].color;
-    }
-    else if (zrUtil.isObject(color) && (color.type === 'pattern')) {
-        finalNearPointColor = 'transparent';
-    }
-    else if (zrUtil.isString(color)) {
-        finalNearPointColor = color;
-    }
-
-    return finalNearPointColor;
-}
-
 function assembleArrow(
     backgroundColor: ColorString,
     borderColor: ZRColor,
@@ -75,7 +61,7 @@ function assembleArrow(
         return '';
     }
 
-    borderColor = getFinalColor(borderColor);
+    borderColor = formatUtil.convertToColorString(borderColor);
     const arrowPos = mirrowPos(arrowPosition);
     let centerPos = '';
     let rotate = 0;
@@ -145,7 +131,7 @@ function assembleCssText(tooltipModel: Model<TooltipOption>, isFirstShow: boolea
     const shadowOffsetX = tooltipModel.get('shadowOffsetX');
     const shadowOffsetY = tooltipModel.get('shadowOffsetY');
     const textStyleModel = tooltipModel.getModel('textStyle');
-    const padding = tooltipModel.get('padding');
+    const padding = getPaddingFromTooltipModel(tooltipModel, 'html');
     const boxShadow = `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px ${shadowColor}`;
 
     cssText.push('box-shadow:' + boxShadow);
@@ -335,7 +321,7 @@ class TooltipHTMLContent {
         const el = this.el;
         const styleCoord = this._styleCoord;
         const offset = el.offsetHeight / 2;
-        nearPointColor = getFinalColor(nearPointColor);
+        nearPointColor = formatUtil.convertToColorString(nearPointColor);
         el.style.cssText = gCssText + assembleCssText(tooltipModel, this._firstShow)
             // Because of the reason described in:
             // http://stackoverflow.com/questions/21125587/css3-transition-not-working-in-chrome-anymore
@@ -359,7 +345,7 @@ class TooltipHTMLContent {
 
     setContent(
         content: string,
-        markers: Dictionary<ColorString>,
+        markers: unknown,
         tooltipModel: Model<TooltipOption>,
         borderColor?: ZRColor,
         arrowPosition?: TooltipOption['position']
@@ -369,11 +355,12 @@ class TooltipHTMLContent {
         }
         this.el.innerHTML = content;
         this.el.innerHTML += (
-                zrUtil.isString(arrowPosition)
-                && tooltipModel.get('trigger') === 'item'
-                && !tooltipModel.get('confine')
-            )
-            ? assembleArrow(tooltipModel.get('backgroundColor'), borderColor, arrowPosition) : '';
+            zrUtil.isString(arrowPosition)
+            && tooltipModel.get('trigger') === 'item'
+            && !shouldTooltipConfine(tooltipModel)
+        )
+            ? assembleArrow(tooltipModel.get('backgroundColor'), borderColor, arrowPosition)
+            : '';
     }
 
     setEnterable(enterable: boolean) {
@@ -389,9 +376,13 @@ class TooltipHTMLContent {
         const styleCoord = this._styleCoord;
         makeStyleCoord(styleCoord, this._zr, this._appendToBody, zrX, zrY);
 
-        const style = this.el.style;
-        style.left = styleCoord[0] + 'px';
-        style.top = styleCoord[1] + 'px';
+        if (styleCoord[0] != null && styleCoord[1] != null) {
+            const style = this.el.style;
+            // If using float on style, the final width of the dom might
+            // keep changing slightly while mouse move. So `toFixed(0)` them.
+            style.left = styleCoord[0].toFixed(0) + 'px';
+            style.top = styleCoord[1].toFixed(0) + 'px';
+        }
     }
 
     hide() {
diff --git a/src/component/tooltip/TooltipModel.ts b/src/component/tooltip/TooltipModel.ts
index 4b6c459..a7bd3e3 100644
--- a/src/component/tooltip/TooltipModel.ts
+++ b/src/component/tooltip/TooltipModel.ts
@@ -97,9 +97,10 @@ class TooltipModel extends ComponentModel<TooltipOption> {
 
         renderMode: 'auto', // 'auto' | 'html' | 'richText'
 
-        // whether restraint content inside viewRect
-        // For compatibility reason, default is false
-        confine: false,
+        // whether restraint content inside viewRect.
+        // If renderMode: 'richText', default true.
+        // If renderMode: 'html', defaut false (for backward compat).
+        confine: null,
 
         showDelay: 0,
 
@@ -129,7 +130,8 @@ class TooltipModel extends ComponentModel<TooltipOption> {
 
         // Tooltip inside padding, default is 5 for all direction
         // Array is allowed to set up, right, bottom, left, same with css
-        padding: 10,
+        // The default value: See `tooltip/tooltipMarkup.ts#getPaddingFromTooltipModel`.
+        padding: null,
 
         // Extra css text
         extraCssText: '',
diff --git a/src/component/tooltip/TooltipRichContent.ts b/src/component/tooltip/TooltipRichContent.ts
index 510b997..d9f9652 100644
--- a/src/component/tooltip/TooltipRichContent.ts
+++ b/src/component/tooltip/TooltipRichContent.ts
@@ -21,10 +21,10 @@ import * as zrUtil from 'zrender/src/core/util';
 import ExtensionAPI from '../../ExtensionAPI';
 import { ZRenderType } from 'zrender/src/zrender';
 import { TooltipOption } from './TooltipModel';
-import { Dictionary } from 'zrender/src/core/types';
-import { ColorString, ZRColor } from '../../util/types';
+import { ZRColor } from '../../util/types';
 import Model from '../../model/Model';
 import ZRText, { TextStyleProps } from 'zrender/src/graphic/Text';
+import { TooltipMarkupStyleCreator, getPaddingFromTooltipModel } from './tooltipMarkup';
 
 class TooltipRichContent {
 
@@ -67,7 +67,7 @@ class TooltipRichContent {
      */
     setContent(
         content: string,
-        markerRich: Dictionary<ColorString>,
+        markupStyleCreator: TooltipMarkupStyleCreator,
         tooltipModel: Model<TooltipOption>,
         borderColor: ZRColor,
         arrowPosition: TooltipOption['position']
@@ -76,43 +76,11 @@ class TooltipRichContent {
             this._zr.remove(this.el);
         }
 
-        const markers: TextStyleProps['rich'] = {};
-        let text = content;
-        const prefix = '{marker';
-        const suffix = '|}';
-        let startId = text.indexOf(prefix);
-        while (startId >= 0) {
-            const endId = text.indexOf(suffix);
-            const name = text.substr(startId + prefix.length, endId - startId - prefix.length);
-            if (name.indexOf('sub') > -1) {
-                markers['marker' + name] = {
-                    width: 4,
-                    height: 4,
-                    borderRadius: 2,
-                    backgroundColor: markerRich[name]
-
-                    // TODO: textOffset is not implemented for rich text
-                    // textOffset: [3, 0]
-                };
-            }
-            else {
-                markers['marker' + name] = {
-                    width: 10,
-                    height: 10,
-                    borderRadius: 5,
-                    backgroundColor: markerRich[name]
-                };
-            }
-
-            text = text.substr(endId + 1);
-            startId = text.indexOf(prefix);
-        }
-
         this.el = new ZRText({
             style: {
-                rich: markers,
+                rich: markupStyleCreator.richTextStyles,
                 text: content,
-                lineHeight: 20,
+                lineHeight: 22,
                 backgroundColor: tooltipModel.get('backgroundColor'),
                 borderRadius: tooltipModel.get('borderRadius'),
                 borderWidth: 1,
@@ -122,7 +90,7 @@ class TooltipRichContent {
                 shadowOffsetX: tooltipModel.get('shadowOffsetX'),
                 shadowOffsetY: tooltipModel.get('shadowOffsetY'),
                 fill: tooltipModel.get(['textStyle', 'color']),
-                padding: tooltipModel.get('padding'),
+                padding: getPaddingFromTooltipModel(tooltipModel, 'richText'),
                 verticalAlign: 'top',
                 align: 'left'
             },
@@ -154,15 +122,26 @@ class TooltipRichContent {
     }
 
     getSize() {
+        const el = this.el;
         const bounding = this.el.getBoundingRect();
-        return [bounding.width, bounding.height];
+        // bounding rect does not include shadow. For renderMode richText,
+        // if overflow, it will be cut. So calculate them accurately.
+        const shadowOuterSize = calcShadowOuterSize(el.style);
+        return [
+            bounding.width + shadowOuterSize.left + shadowOuterSize.right,
+            bounding.height + shadowOuterSize.top + shadowOuterSize.bottom
+        ];
     }
 
     moveTo(x: number, y: number) {
         const el = this.el;
         if (el) {
-            el.x = x;
-            el.y = y;
+            const style = el.style;
+            const borderWidth = mathMaxWith0(style.borderWidth || 0);
+            const shadowOuterSize = calcShadowOuterSize(style);
+            // rich text x, y do not include border.
+            el.x = x + borderWidth + shadowOuterSize.left;
+            el.y = y + borderWidth + shadowOuterSize.top;
             el.markRedraw();
         }
     }
@@ -205,5 +184,20 @@ class TooltipRichContent {
     }
 }
 
+function mathMaxWith0(val: number): number {
+    return Math.max(0, val);
+}
+
+function calcShadowOuterSize(style: TextStyleProps) {
+    const shadowBlur = mathMaxWith0(style.shadowBlur || 0);
+    const shadowOffsetX = mathMaxWith0(style.shadowOffsetX || 0);
+    const shadowOffsetY = mathMaxWith0(style.shadowOffsetY || 0);
+    return {
+        left: mathMaxWith0(shadowBlur - shadowOffsetX),
+        right: mathMaxWith0(shadowBlur + shadowOffsetX),
+        top: mathMaxWith0(shadowBlur - shadowOffsetY),
+        bottom: mathMaxWith0(shadowBlur + shadowOffsetY)
+    };
+}
 
 export default TooltipRichContent;
diff --git a/src/component/tooltip/TooltipView.ts b/src/component/tooltip/TooltipView.ts
index c88bec3..d877177 100644
--- a/src/component/tooltip/TooltipView.ts
+++ b/src/component/tooltip/TooltipView.ts
@@ -47,11 +47,13 @@ import GlobalModel from '../../model/Global';
 import ExtensionAPI from '../../ExtensionAPI';
 import TooltipModel, {TooltipOption} from './TooltipModel';
 import Element from 'zrender/src/Element';
-import { Dictionary } from 'zrender/src/core/types';
 import { AxisBaseModel } from '../../coord/AxisBaseModel';
-import { CoordinateSystem } from '../../coord/CoordinateSystem';
 // import { isDimensionStacked } from '../../data/helper/dataStackHelper';
 import { getECData } from '../../util/ecData';
+import { shouldTooltipConfine } from './helper';
+import { DataByCoordSys, DataByAxis } from '../axisPointer/axisTrigger';
+import { normalizeTooltipFormatResult } from '../../model/mixin/dataFormat';
+import { createTooltipMarkup, buildTooltipMarkup, TooltipMarkupStyleCreator } from './tooltipMarkup';
 
 const bind = zrUtil.bind;
 const each = zrUtil.each;
@@ -67,19 +69,6 @@ interface DataIndex {
 
     dataIndexInside: number
 }
-interface DataByAxis {
-    // TODO: TYPE Value type
-    value: string | number
-    axisIndex: number
-    axisDim: string
-    axisType: string
-    axisId: string
-
-    seriesDataIndices: DataIndex[]
-}
-interface DataByCoordSys {
-    dataByAxis: DataByAxis[]
-}
 
 interface ShowTipPayload {
     type?: 'showTip'
@@ -90,7 +79,7 @@ interface ShowTipPayload {
 
     // Type 2
     dataByCoordSys?: DataByCoordSys[]
-    tooltipOption?: CommonTooltipOption<TooltipDataParams | TooltipDataParams[]>
+    tooltipOption?: CommonTooltipOption<TooltipCallbackDataParams | TooltipCallbackDataParams[]>
 
     // Type 3
     seriesIndex?: number
@@ -122,12 +111,12 @@ interface TryShowParams {
      */
     dataByCoordSys?: DataByCoordSys[]
 
-    tooltipOption?: CommonTooltipOption<TooltipDataParams | TooltipDataParams[]>
+    tooltipOption?: CommonTooltipOption<TooltipCallbackDataParams | TooltipCallbackDataParams[]>
 
     position?: TooltipOption['position']
 }
 
-type TooltipDataParams = CallbackDataParams & {
+type TooltipCallbackDataParams = CallbackDataParams & {
     axisDim?: string
     axisIndex?: number
     axisType?: string
@@ -136,19 +125,14 @@ type TooltipDataParams = CallbackDataParams & {
     axisValue?: string | number
     axisValueLabel?: string
     marker?: formatUtil.TooltipMarker
-    // params below should not be exposed to callback
-    html?: string
-    position?: number[]
-    coordinateSystem?: CoordinateSystem
 };
+
 class TooltipView extends ComponentView {
     static type = 'tooltip' as const;
     type = TooltipView.type;
 
     private _renderMode: TooltipRenderMode;
 
-    private _newLine: '' | '\n';
-
     private _tooltipModel: TooltipModel;
 
     private _ecModel: GlobalModel;
@@ -179,19 +163,11 @@ class TooltipView extends ComponentView {
         const renderMode = tooltipModel.get('renderMode');
         this._renderMode = getTooltipRenderMode(renderMode);
 
-        let tooltipContent;
-        if (this._renderMode === 'html') {
-            tooltipContent = new TooltipHTMLContent(api.getDom(), api, {
+        this._tooltipContent = this._renderMode === 'richText'
+            ? new TooltipRichContent(api)
+            : new TooltipHTMLContent(api.getDom(), api, {
                 appendToBody: tooltipModel.get('appendToBody', true)
             });
-            this._newLine = '';
-        }
-        else {
-            tooltipContent = new TooltipRichContent(api);
-            this._newLine = '\n';
-        }
-
-        this._tooltipContent = tooltipContent;
     }
 
     render(
@@ -227,7 +203,7 @@ class TooltipView extends ComponentView {
         this._keepShow();
     }
 
-    _initGlobalListener() {
+    private _initGlobalListener() {
         const tooltipModel = this._tooltipModel;
         const triggerOn = tooltipModel.get('triggerOn');
 
@@ -248,7 +224,7 @@ class TooltipView extends ComponentView {
         );
     }
 
-    _keepShow() {
+    private _keepShow() {
         const tooltipModel = this._tooltipModel;
         const ecModel = this._ecModel;
         const api = this._api;
@@ -390,7 +366,7 @@ class TooltipView extends ComponentView {
     // Be compatible with previous design, that is, when tooltip.type is 'axis' and
     // dispatchAction 'showTip' with seriesIndex and dataIndex will trigger axis pointer
     // and tooltip.
-    _manuallyAxisShowTip(
+    private _manuallyAxisShowTip(
         tooltipModel: TooltipModel,
         ecModel: GlobalModel,
         api: ExtensionAPI,
@@ -432,7 +408,7 @@ class TooltipView extends ComponentView {
         return true;
     }
 
-    _tryShow(
+    private _tryShow(
         e: TryShowParams,
         dispatchAction: ExtensionAPI['dispatchAction']
     ) {
@@ -467,7 +443,7 @@ class TooltipView extends ComponentView {
         }
     }
 
-    _showOrMove(
+    private _showOrMove(
         tooltipModel: Model<TooltipOption>,
         cb: () => void
     ) {
@@ -483,26 +459,26 @@ class TooltipView extends ComponentView {
             : cb();
     }
 
-    _showAxisTooltip(
+    private _showAxisTooltip(
         dataByCoordSys: DataByCoordSys[],
         e: TryShowParams
     ) {
         const ecModel = this._ecModel;
         const globalTooltipModel = this._tooltipModel;
-
         const point = [e.offsetX, e.offsetY];
-
-        const singleDefaultHTML: string[] = [];
-        const singleParamsList: TooltipDataParams[] = [];
         const singleTooltipModel = buildTooltipModel([
             e.tooltipOption,
             globalTooltipModel
         ]);
-
         const renderMode = this._renderMode;
-        const newLine = this._newLine;
-
-        const markers = {};
+        const cbParamsList: TooltipCallbackDataParams[] = [];
+        const articleMarkup = createTooltipMarkup('section', {
+            blocks: [],
+            noHeader: true
+        });
+        // Only for legacy: `Serise['formatTooltip']` returns a string.
+        const markupTextArrLegacy: string[] = [];
+        const markupStyleCreator = new TooltipMarkupStyleCreator();
 
         each(dataByCoordSys, function (itemCoordSys) {
             // let coordParamList = [];
@@ -514,54 +490,56 @@ class TooltipView extends ComponentView {
             //     globalTooltipModel
             // ]);
             // let displayMode = coordTooltipModel.get('displayMode');
-            // let paramsList = displayMode === 'single' ? singleParamsList : [];
-
-            each(itemCoordSys.dataByAxis, function (item) {
-                const axisModel = ecModel.getComponent(item.axisDim + 'Axis', item.axisIndex) as AxisBaseModel;
-                const axisValue = item.value;
-                const seriesDefaultHTML: string[] = [];
+            // let paramsList = displayMode === 'single' ? infoBySeriesList : [];
 
+            each(itemCoordSys.dataByAxis, function (axisItem) {
+                const axisModel = ecModel.getComponent(axisItem.axisDim + 'Axis', axisItem.axisIndex) as AxisBaseModel;
+                const axisValue = axisItem.value;
                 if (!axisModel || axisValue == null) {
                     return;
                 }
-
-                const valueLabel = axisPointerViewHelper.getValueLabel(
+                const axisValueLabel = axisPointerViewHelper.getValueLabel(
                     axisValue, axisModel.axis, ecModel,
-                    item.seriesDataIndices,
-                    // @ts-ignore
-                    item.valueLabelOpt
+                    axisItem.seriesDataIndices,
+                    axisItem.valueLabelOpt
                 );
+                const axisSectionMarkup = createTooltipMarkup('section', {
+                    header: axisValueLabel,
+                    noHeader: !zrUtil.trim(axisValueLabel),
+                    sortBlocks: true,
+                    blocks: []
+                });
+                articleMarkup.blocks.push(axisSectionMarkup);
 
-                zrUtil.each(item.seriesDataIndices, function (idxItem) {
+                zrUtil.each(axisItem.seriesDataIndices, function (idxItem) {
                     const series = ecModel.getSeriesByIndex(idxItem.seriesIndex);
                     const dataIndex = idxItem.dataIndexInside;
-                    const dataParams = series && series.getDataParams(dataIndex) as TooltipDataParams;
-                    dataParams.axisDim = item.axisDim;
-                    dataParams.axisIndex = item.axisIndex;
-                    dataParams.axisType = item.axisType;
-                    dataParams.axisId = item.axisId;
-                    dataParams.axisValue = axisHelper.getAxisRawValue(axisModel.axis, { value: axisValue as number });
-                    dataParams.axisValueLabel = valueLabel;
-                    dataParams.marker = formatUtil.getTooltipMarker({
-                        color: dataParams.color as ColorString,
-                        renderMode
-                    });
-
-                    singleParamsList.push(dataParams);
-                    const seriesTooltip = series.formatTooltip(
-                        dataIndex, true, null, renderMode as TooltipRenderMode
+                    const cbParams = series.getDataParams(dataIndex) as TooltipCallbackDataParams;
+                    cbParams.axisDim = axisItem.axisDim;
+                    cbParams.axisIndex = axisItem.axisIndex;
+                    cbParams.axisType = axisItem.axisType;
+                    cbParams.axisId = axisItem.axisId;
+                    cbParams.axisValue = axisHelper.getAxisRawValue(
+                        axisModel.axis, { value: axisValue as number }
+                    );
+                    cbParams.axisValueLabel = axisValueLabel;
+                    // Pre-create marker style for makers. Users can assemble richText
+                    // text in `formatter` callback and use those markers style.
+                    cbParams.marker = markupStyleCreator.makeTooltipMarker(
+                        'item', formatUtil.convertToColorString(cbParams.color), renderMode
                     );
 
-                    let html;
-                    if (zrUtil.isObject(seriesTooltip)) {
-                        html = seriesTooltip.html;
-                        const newMarkers = seriesTooltip.markers;
-                        zrUtil.merge(markers, newMarkers);
+                    const seriesTooltipResult = normalizeTooltipFormatResult(
+                        series.formatTooltip(dataIndex, true, null)
+                    );
+                    if (seriesTooltipResult.markupFragment) {
+                        axisSectionMarkup.blocks.push(seriesTooltipResult.markupFragment);
                     }
-                    else {
-                        html = seriesTooltip;
+                    if (seriesTooltipResult.markupText) {
+                        markupTextArrLegacy.push(seriesTooltipResult.markupText);
                     }
-                    dataParams.html = html;
+                    cbParamsList.push(cbParams);
+
                     // const data = series.getData();
                     // const dims = zrUtil.map(series.coordinateSystem.dimensions, function (coordDim) {
                     //     return data.mapDimension(coordDim);
@@ -583,65 +561,24 @@ class TooltipView extends ComponentView {
                     //     isStacked
                     // }, ecModel).point;
                 });
-
-                switch (singleTooltipModel.get('order')) {
-                    case 'valueAsc':
-                        singleParamsList.sort(function (a, b) {
-                            return +(a.data) - +(b.data);
-                        });
-                        break;
-
-                    case 'valueDesc':
-                        singleParamsList.sort(function (a, b) {
-                            return +(b.data) - +(a.data);
-                        });
-                        break;
-
-                    case 'seriesDesc':
-                        singleParamsList.reverse();
-                        break;
-
-                    case 'seriesAsc':
-                    default:
-                        break;
-                }
-
-                zrUtil.each(singleParamsList, function (params) {
-                    seriesDefaultHTML.push(params.html);
-                    delete params.html;
-                });
-
-                // Default tooltip content
-                // FIXME
-                // (1) shold be the first data which has name?
-                // (2) themeRiver, firstDataIndex is array, and first line is unnecessary.
-                const firstLine = valueLabel;
-                if (renderMode !== 'html') {
-                    singleDefaultHTML.push(seriesDefaultHTML.join(newLine));
-                }
-                else {
-                    singleDefaultHTML.push(
-                        (
-                            firstLine
-                                ? (
-                                    '<div style="font-size:12px;color:#6e7079;line-height:1;margin-top:-4px;">'
-                                    + formatUtil.encodeHTML(firstLine) + '</div>'
-                                    + newLine
-                                )
-                                : ''
-                        )
-                        + seriesDefaultHTML.reverse().join(newLine)
-                    );
-                }
-                singleDefaultHTML.push('<br/>');
             });
-        }, this);
+        });
 
-        // In most case, the second axis is shown upper than the first one.
-        singleDefaultHTML.pop();
-        const singleDefaultHTMLStr = singleDefaultHTML.join(this._newLine + this._newLine);
+        // In most cases, the second axis is displays upper on the first one.
+        // So we reverse it to look better.
+        articleMarkup.blocks.reverse();
+        markupTextArrLegacy.reverse();
 
         const positionExpr = e.position;
+        const orderMode = singleTooltipModel.get('order');
+
+        const builtMarkupText = buildTooltipMarkup(
+            articleMarkup, markupStyleCreator, renderMode, orderMode
+        );
+        builtMarkupText && markupTextArrLegacy.unshift(builtMarkupText);
+        const blockBreak = renderMode === 'richText' ? '\n\n' : '<br/>';
+        const allMarkupText = markupTextArrLegacy.join(blockBreak);
+
         this._showOrMove(singleTooltipModel, function (this: TooltipView) {
             if (this._updateContentNotChangedOnAxis(dataByCoordSys)) {
                 this._updatePosition(
@@ -649,13 +586,13 @@ class TooltipView extends ComponentView {
                     positionExpr,
                     point[0], point[1],
                     this._tooltipContent,
-                    singleParamsList
+                    cbParamsList
                 );
             }
             else {
                 this._showTooltipContent(
-                    singleTooltipModel, singleDefaultHTMLStr, singleParamsList, Math.random() + '',
-                    point[0], point[1], positionExpr, undefined, markers
+                    singleTooltipModel, allMarkupText, cbParamsList, Math.random() + '',
+                    point[0], point[1], positionExpr, null, markupStyleCreator
                 );
             }
         });
@@ -664,7 +601,7 @@ class TooltipView extends ComponentView {
         // from dispatchAction.
     }
 
-    _showSeriesItemTooltip(
+    private _showSeriesItemTooltip(
         e: TryShowParams,
         el: ECElement,
         dispatchAction: ExtensionAPI['dispatchAction']
@@ -682,6 +619,7 @@ class TooltipView extends ComponentView {
         const dataIndex = ecData.dataIndex;
         const dataType = ecData.dataType;
         const data = dataModel.getData(dataType);
+        const renderMode = this._renderMode;
 
         const tooltipModel = buildTooltipModel([
             data.getItemModel<TooltipableOption>(dataIndex),
@@ -694,32 +632,35 @@ class TooltipView extends ComponentView {
         if (tooltipTrigger != null && tooltipTrigger !== 'item') {
             return;
         }
-        const tooltipOrder = tooltipModel.get('order');
 
         const params = dataModel.getDataParams(dataIndex, dataType);
-        params.marker = formatUtil.getTooltipMarker({
-            color: params.color as ColorString,
-            renderMode: this._renderMode
-        });
+        const markupStyleCreator = new TooltipMarkupStyleCreator();
+        // Pre-create marker style for makers. Users can assemble richText
+        // text in `formatter` callback and use those markers style.
+        params.marker = markupStyleCreator.makeTooltipMarker(
+            'item', formatUtil.convertToColorString(params.color), renderMode
+        );
 
-        const seriesTooltip = dataModel.formatTooltip(dataIndex, false, dataType, this._renderMode, tooltipOrder);
-        let defaultHtml: string;
-        let markers: Dictionary<ColorString>;
-        if (zrUtil.isObject(seriesTooltip)) {
-            defaultHtml = seriesTooltip.html;
-            markers = seriesTooltip.markers;
-        }
-        else {
-            defaultHtml = seriesTooltip;
-            markers = null;
-        }
+        const seriesTooltipResult = normalizeTooltipFormatResult(
+            dataModel.formatTooltip(dataIndex, false, dataType)
+        );
+        const orderMode = tooltipModel.get('order');
+        const markupText = seriesTooltipResult.markupFragment
+            ? buildTooltipMarkup(
+                seriesTooltipResult.markupFragment,
+                markupStyleCreator,
+                renderMode,
+                orderMode
+            )
+            : seriesTooltipResult.markupText;
 
         const asyncTicket = 'item_' + dataModel.name + '_' + dataIndex;
 
         this._showOrMove(tooltipModel, function (this: TooltipView) {
             this._showTooltipContent(
-                tooltipModel, defaultHtml, params, asyncTicket,
-                e.offsetX, e.offsetY, e.position, e.target, markers
+                tooltipModel, markupText, params, asyncTicket,
+                e.offsetX, e.offsetY, e.position, e.target,
+                markupStyleCreator
             );
         });
 
@@ -734,7 +675,7 @@ class TooltipView extends ComponentView {
         });
     }
 
-    _showComponentItemTooltip(
+    private _showComponentItemTooltip(
         e: TryShowParams,
         el: ECElement,
         dispatchAction: ExtensionAPI['dispatchAction']
@@ -751,6 +692,8 @@ class TooltipView extends ComponentView {
         const subTooltipModel = new Model(tooltipOpt, this._tooltipModel, this._ecModel);
         const defaultHtml = subTooltipModel.get('content');
         const asyncTicket = Math.random() + '';
+        // PENDING: this case do not support richText style yet.
+        const markupStyleCreator = new TooltipMarkupStyleCreator();
 
         // Do not check whether `trigger` is 'none' here, because `trigger`
         // only works on cooridinate system. In fact, we have not found case
@@ -760,7 +703,7 @@ class TooltipView extends ComponentView {
             this._showTooltipContent(
                 // Use formatterParams from element defined in component
                 subTooltipModel, defaultHtml, subTooltipModel.get('formatterParams') as any || {},
-                asyncTicket, e.offsetX, e.offsetY, e.position, el
+                asyncTicket, e.offsetX, e.offsetY, e.position, el, markupStyleCreator
             );
         });
 
@@ -771,18 +714,18 @@ class TooltipView extends ComponentView {
         });
     }
 
-    _showTooltipContent(
+    private _showTooltipContent(
         // Use Model<TooltipOption> insteadof TooltipModel because this model may be from series or other options.
         // Instead of top level tooltip.
         tooltipModel: Model<TooltipOption>,
         defaultHtml: string,
-        params: TooltipDataParams | TooltipDataParams[],
+        params: TooltipCallbackDataParams | TooltipCallbackDataParams[],
         asyncTicket: string,
         x: number,
         y: number,
         positionExpr: TooltipOption['position'],
-        el?: ECElement,
-        markers?: Dictionary<ColorString>
+        el: ECElement,
+        markupStyleCreator: TooltipMarkupStyleCreator
     ) {
         // Reset ticket
         this._ticket = '';
@@ -808,7 +751,7 @@ class TooltipView extends ComponentView {
         else if (zrUtil.isFunction(formatter)) {
             const callback = bind(function (cbTicket: string, html: string) {
                 if (cbTicket === this._ticket) {
-                    tooltipContent.setContent(html, markers, tooltipModel, nearPoint.color, positionExpr);
+                    tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPoint.color, positionExpr);
                     this._updatePosition(
                         tooltipModel, positionExpr, x, y, tooltipContent, params, el
                     );
@@ -818,7 +761,7 @@ class TooltipView extends ComponentView {
             html = formatter(params, asyncTicket, callback);
         }
 
-        tooltipContent.setContent(html, markers, tooltipModel, nearPoint.color, positionExpr);
+        tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPoint.color, positionExpr);
         tooltipContent.show(tooltipModel, nearPoint.color);
         this._updatePosition(
             tooltipModel, positionExpr, x, y, tooltipContent, params, el
@@ -826,9 +769,9 @@ class TooltipView extends ComponentView {
 
     }
 
-    _getNearestPoint(
+    private _getNearestPoint(
         point: number[],
-        tooltipDataParams: TooltipDataParams | TooltipDataParams[],
+        tooltipDataParams: TooltipCallbackDataParams | TooltipCallbackDataParams[],
         trigger: TooltipOption['trigger']
     ): {
         color: ZRColor;
@@ -874,13 +817,13 @@ class TooltipView extends ComponentView {
         // };
     }
 
-    _updatePosition(
+    private _updatePosition(
         tooltipModel: Model<TooltipOption>,
         positionExpr: TooltipOption['position'],
         x: number,  // Mouse x
         y: number,  // Mouse y
         content: TooltipHTMLContent | TooltipRichContent,
-        params: TooltipDataParams | TooltipDataParams[],
+        params: TooltipCallbackDataParams | TooltipCallbackDataParams[],
         el?: Element
     ) {
         const viewWidth = this._api.getWidth();
@@ -939,7 +882,7 @@ class TooltipView extends ComponentView {
         align && (x -= isCenterAlign(align) ? contentSize[0] / 2 : align === 'right' ? contentSize[0] : 0);
         vAlign && (y -= isCenterAlign(vAlign) ? contentSize[1] / 2 : vAlign === 'bottom' ? contentSize[1] : 0);
 
-        if (tooltipModel.get('confine')) {
+        if (shouldTooltipConfine(tooltipModel)) {
             const pos = confineTooltipPosition(
                 x, y, content, viewWidth, viewHeight
             );
@@ -952,7 +895,7 @@ class TooltipView extends ComponentView {
 
     // FIXME
     // Should we remove this but leave this to user?
-    _updateContentNotChangedOnAxis(dataByCoordSys: DataByCoordSys[]) {
+    private _updateContentNotChangedOnAxis(dataByCoordSys: DataByCoordSys[]) {
         const lastCoordSys = this._lastDataByCoordSys;
         let contentNotChanged = !!lastCoordSys
             && lastCoordSys.length === dataByCoordSys.length;
@@ -988,7 +931,7 @@ class TooltipView extends ComponentView {
         return !!contentNotChanged;
     }
 
-    _hide(dispatchAction: ExtensionAPI['dispatchAction']) {
+    private _hide(dispatchAction: ExtensionAPI['dispatchAction']) {
         // Do not directly hideLater here, because this behavior may be prevented
         // in dispatchAction when showTip is dispatched.
 
@@ -1056,7 +999,11 @@ function refixTooltipPosition(
     const height = size.height;
 
     if (gapH != null) {
-        if (x + width + gapH > viewWidth) {
+        // Add extra 2 pixels for this case:
+        // At present the "values" in defaut tooltip are using CSS `float: right`.
+        // When the right edge of the tooltip box is on the right side of the
+        // viewport, the `float` layout might push the "values" to the second line.
+        if (x + width + gapH + 2 > viewWidth) {
             x -= width + gapH;
         }
         else {
diff --git a/src/coord/radar/IndicatorAxis.ts b/src/component/tooltip/helper.ts
similarity index 58%
copy from src/coord/radar/IndicatorAxis.ts
copy to src/component/tooltip/helper.ts
index 116e166..3520056 100644
--- a/src/coord/radar/IndicatorAxis.ts
+++ b/src/component/tooltip/helper.ts
@@ -17,27 +17,13 @@
 * under the License.
 */
 
-import Axis from '../Axis';
-import Scale from '../../scale/Scale';
-import { OptionAxisType } from '../axisCommonTypes';
-import { AxisBaseModel } from '../AxisBaseModel';
-import { InnerIndicatorAxisOption } from './RadarModel';
-
-class IndicatorAxis extends Axis {
-
-    type: OptionAxisType = 'value';
-
-    angle = 0;
-
-    name = '';
-
-    model: AxisBaseModel<InnerIndicatorAxisOption>;
-
-    value?: number | string;
-
-    constructor(dim: string, scale: Scale, radiusExtent?: [number, number]) {
-        super(dim, scale, radiusExtent);
-    }
+import { TooltipOption } from './TooltipModel';
+import Model from '../../model/Model';
+
+export function shouldTooltipConfine(tooltipModel: Model<TooltipOption>): boolean {
+    const confineOption = tooltipModel.get('confine');
+    return confineOption != null
+        ? !!confineOption
+        // In richText mode, the outside part can not be visible.
+        : tooltipModel.get('renderMode') === 'richText';
 }
-
-export default IndicatorAxis;
\ No newline at end of file
diff --git a/src/component/tooltip/seriesFormatTooltip.ts b/src/component/tooltip/seriesFormatTooltip.ts
new file mode 100644
index 0000000..18197be
--- /dev/null
+++ b/src/component/tooltip/seriesFormatTooltip.ts
@@ -0,0 +1,154 @@
+/*
+* 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 SeriesModel from '../../model/Series';
+import { trim, isArray, each, reduce } from 'zrender/src/core/util';
+import { DimensionName, DimensionType, ColorString } from '../../util/types';
+import {
+    retrieveVisualColorForTooltipMarker,
+    TooltipMarkupBlockFragment,
+    createTooltipMarkup,
+    TooltipMarkupSection
+} from './tooltipMarkup';
+import { retrieveRawValue } from '../../data/helper/dataProvider';
+import { isNameSpecified } from '../../util/model';
+
+
+export function defaultSeriesFormatTooltip(opt: {
+    series: SeriesModel;
+    dataIndex: number;
+    // `multipleSeries` means multiple series displayed in one tooltip,
+    // and this method only return the part of one series.
+    multipleSeries: boolean;
+}): TooltipMarkupSection {
+    const series = opt.series;
+    const dataIndex = opt.dataIndex;
+    const multipleSeries = opt.multipleSeries;
+
+    const data = series.getData();
+    const tooltipDims = data.mapDimensionsAll('defaultedTooltip');
+    const tooltipDimLen = tooltipDims.length;
+    const value = series.getRawValue(dataIndex) as any;
+    const isValueArr = isArray(value);
+    const markerColor = retrieveVisualColorForTooltipMarker(series, dataIndex);
+
+    // Complicated rule for pretty tooltip.
+    let inlineValue;
+    let inlineValueType: DimensionType | DimensionType[];
+    let subBlocks: TooltipMarkupBlockFragment[];
+    let sortParam: unknown;
+    if (tooltipDimLen > 1 || (isValueArr && !tooltipDimLen)) {
+        const formatArrResult = formatTooltipArrayValue(value, series, dataIndex, tooltipDims, markerColor);
+        inlineValue = formatArrResult.inlineValues;
+        inlineValueType = formatArrResult.inlineValueTypes;
+        subBlocks = formatArrResult.blocks;
+        // Only support tooltip sort by the first inline value. It's enough in most cases.
+        sortParam = formatArrResult.inlineValues[0];
+    }
+    else if (tooltipDimLen) {
+        const dimInfo = data.getDimensionInfo(tooltipDims[0]);
+        sortParam = inlineValue = retrieveRawValue(data, dataIndex, tooltipDims[0]);
+        inlineValueType = dimInfo.type;
+    }
+    else {
+        sortParam = inlineValue = isValueArr ? value[0] : value;
+    }
+
+    // Do not show generated series name. It might not be readable.
+    const seriesNameSpecified = isNameSpecified(series);
+    const seriesName = seriesNameSpecified && series.name || '';
+    const itemName = data.getName(dataIndex);
+    const inlineName = multipleSeries ? seriesName : itemName;
+
+    return createTooltipMarkup('section', {
+        header: seriesName,
+        // When series name not specified, do not show a header line with only '-'.
+        // This case alway happen in tooltip.trigger: 'item'.
+        noHeader: multipleSeries || !seriesNameSpecified,
+        sortParam: sortParam,
+        blocks: [
+            createTooltipMarkup('nameValue', {
+                markerType: 'item',
+                markerColor: markerColor,
+                // Do not mix display seriesName and itemName in one tooltip,
+                // which might confuses users.
+                name: inlineName,
+                // name dimension might be auto assigned, where the name might
+                // be not readable. So we check trim here.
+                noName: !trim(inlineName),
+                value: inlineValue,
+                valueType: inlineValueType
+            })
+        ].concat(subBlocks || [] as any)
+    });
+}
+
+function formatTooltipArrayValue(
+    value: unknown[],
+    series: SeriesModel,
+    dataIndex: number,
+    tooltipDims: DimensionName[],
+    colorStr: ColorString
+): {
+    inlineValues: unknown[];
+    inlineValueTypes: DimensionType[];
+    blocks: TooltipMarkupBlockFragment[];
+} {
+    // check: category-no-encode-has-axis-data in dataset.html
+    const data = series.getData();
+    const isValueMultipleLine = reduce(value, function (isValueMultipleLine, val, idx) {
+        const dimItem = data.getDimensionInfo(idx);
+        return isValueMultipleLine = isValueMultipleLine
+            || (dimItem && dimItem.tooltip !== false && dimItem.displayName != null);
+    }, false);
+
+    const inlineValues: unknown[] = [];
+    const inlineValueTypes: DimensionType[] = [];
+    const blocks: TooltipMarkupBlockFragment[] = [];
+
+    tooltipDims.length
+        ? each(tooltipDims, function (dim) {
+            setEachItem(retrieveRawValue(data, dataIndex, dim), dim);
+        })
+        // By default, all dims is used on tooltip.
+        : each(value, setEachItem);
+
+    function setEachItem(val: unknown, dim: DimensionName | number): void {
+        const dimInfo = data.getDimensionInfo(dim);
+        // If `dimInfo.tooltip` is not set, show tooltip.
+        if (!dimInfo || dimInfo.otherDims.tooltip === false) {
+            return;
+        }
+        if (isValueMultipleLine) {
+            blocks.push(createTooltipMarkup('nameValue', {
+                markerType: 'subItem',
+                markerColor: colorStr,
+                name: dimInfo.displayName,
+                value: val,
+                valueType: dimInfo.type
+            }));
+        }
+        else {
+            inlineValues.push(val);
+            inlineValueTypes.push(dimInfo.type);
+        }
+    }
+
+    return { inlineValues, inlineValueTypes, blocks };
+}
diff --git a/src/component/tooltip/tooltipMarkup.ts b/src/component/tooltip/tooltipMarkup.ts
new file mode 100644
index 0000000..40f78c4
--- /dev/null
+++ b/src/component/tooltip/tooltipMarkup.ts
@@ -0,0 +1,529 @@
+/*
+* 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 {
+    Dictionary, TooltipRenderMode, ColorString,
+    TooltipOrderMode, DimensionType
+} from '../../util/types';
+import {
+    TooltipMarkerType, getTooltipMarker, encodeHTML,
+    makeValueReadable, convertToColorString
+} from '../../util/format';
+import { isString, each, hasOwn, isArray, map, assert, extend } from 'zrender/src/core/util';
+import { SortOrderComparator } from '../../data/helper/dataValueHelper';
+import SeriesModel from '../../model/Series';
+import { getRandomIdBase } from '../../util/number';
+import Model from '../../model/Model';
+import { TooltipOption } from './TooltipModel';
+
+
+const TOOLTIP_NAME_TEXT_STYLE_CSS = 'font-size:12px;color:#6e7079';
+const TOOLTIP_TEXT_STYLE_RICH = {
+    fontSize: 12,
+    fill: '#6e7079'
+};
+const TOOLTIP_VALUE_TEXT_STYLE_CSS = 'font-size:14px;color:#464646;font-weight:900';
+const TOOLTIP_VALUE_TEXT_STYLE_RICH = {
+    fontSize: 14,
+    fill: '#464646',
+    fontWeight: 900
+};
+const TOOLTIP_LINE_HEIGHT_CSS = 'line-height:1';
+
+// 0: no gap in this block.
+// 1: has max gap in level 1 in this block.
+// ...
+type GapLevel = number;
+// See `TooltipMarkupLayoutIntent['innerGapLevel']`.
+// (value from UI design)
+const HTML_GAPS: { [key in GapLevel]: number } = [0, 10, 20, 30];
+const RICH_TEXT_GAPS: { [key in GapLevel]: string } = ['', '\n', '\n\n', '\n\n\n'];
+
+/**
+ * This is an abstract layer to insulate the upper usage of tooltip content
+ * from the different backends according to different `renderMode` ('html' or 'richText').
+ * With the help of the abstract layer, it does not need to consider how to create and
+ * assemble html or richText snippets when making tooltip content.
+ *
+ * @usage
+ *
+ * ```ts
+ * class XxxSeriesModel {
+ *     formatTooltip(
+ *         dataIndex: number,
+ *         multipleSeries: boolean,
+ *         dataType: string
+ *     ) {
+ *         ...
+ *         return createTooltipMarkup('section', {
+ *             header: header,
+ *             blocks: [
+ *                 createTooltipMarkup('nameValue', {
+ *                     name: name,
+ *                     value: value,
+ *                     noValue: value == null
+ *                 })
+ *             ]
+ *         });
+ *     }
+ * }
+ * ```
+ */
+export type TooltipMarkupBlockFragment =
+    TooltipMarkupSection
+    | TooltipMarkupNameValueBlock;
+
+interface TooltipMarkupBlock {
+    // Use to make comparison when `sortBlocks: true`.
+    sortParam?: unknown;
+    __gapLevelBetweenSubBlocks?: number;
+}
+
+export interface TooltipMarkupSection extends TooltipMarkupBlock {
+    type: 'section';
+    header?: unknown;
+    // If `noHeader` is `true`, do not display header.
+    // Otherwise, always display it even if it is
+    // null/undefined/NaN/''... (displayed as '-').
+    noHeader?: boolean;
+    blocks?: TooltipMarkupBlockFragment[];
+    // Enable to sort blocks when making final html or richText.
+    sortBlocks?: boolean;
+}
+
+export interface TooltipMarkupNameValueBlock extends TooltipMarkupBlock {
+    type: 'nameValue';
+    // If `!markerType`, tooltip marker is not used.
+    markerType?: TooltipMarkerType;
+    markerColor?: ColorString;
+    name?: string;
+    // Also support value is `[121, 555, 94.2]`.
+    value?: unknown | unknown[];
+    // If not specified, treat value as normal string or numeric.
+    // If needs to display formatted time, set as 'time'.
+    // If needs to display original string with numeric guessing, set as 'ordinal'.
+    // If both `value` and `valueType` are array, each valueType[i] cooresponds to value[i].
+    valueType?: DimensionType | DimensionType[];
+    // If `noName` or `noValue` is `true`, do not display name or value.
+    // Otherwise, always display them even if they are
+    // null/undefined/NaN/''... (displayed as '-').
+    noName?: boolean;
+    noValue?: boolean;
+}
+
+/**
+ * Create tooltip markup by this function, we can get TS type check.
+ */
+// eslint-disable-next-line max-len
+export function createTooltipMarkup(type: 'section', option: Omit<TooltipMarkupSection, 'type'>): TooltipMarkupSection;
+// eslint-disable-next-line max-len
+export function createTooltipMarkup(type: 'nameValue', option: Omit<TooltipMarkupNameValueBlock, 'type'>): TooltipMarkupNameValueBlock;
+// eslint-disable-next-line max-len
+export function createTooltipMarkup(type: TooltipMarkupBlockFragment['type'], option: Omit<TooltipMarkupBlockFragment, 'type'>): TooltipMarkupBlockFragment {
+    (option as TooltipMarkupBlockFragment).type = type;
+    return option as TooltipMarkupBlockFragment;
+}
+
+
+// Can be null/undefined, which means generate nothing markup text.
+type MarkupText = string;
+interface TooltipMarkupFragmentBuilder {
+    planLayout(
+        fragment: TooltipMarkupBlockFragment
+    ): void;
+    build(
+        ctx: TooltipMarkupBuildContext,
+        fragment: TooltipMarkupBlockFragment,
+        topMarginForOuterGap: number
+    ): MarkupText;
+}
+
+function getBuilder(fragment: TooltipMarkupBlockFragment): TooltipMarkupFragmentBuilder {
+    return hasOwn(builderMap, fragment.type) && builderMap[fragment.type];
+}
+
+const builderMap: { [key in TooltipMarkupBlockFragment['type']]: TooltipMarkupFragmentBuilder } = {
+
+    /**
+     * A `section` block is like:
+     * ```
+     * header
+     * subBlock
+     * subBlock
+     * ...
+     * ```
+     */
+    section: {
+        planLayout: function (fragment: TooltipMarkupSection) {
+            const subBlockLen = fragment.blocks.length;
+            const thisBlockHasInnerGap = subBlockLen > 1 || (subBlockLen > 0 && !fragment.noHeader);
+
+            let thisGapLevelBetweenSubBlocks = 0;
+            each(fragment.blocks, function (subBlock) {
+                getBuilder(subBlock).planLayout(subBlock);
+                const subGapLevel = subBlock.__gapLevelBetweenSubBlocks;
+
+                // If the some of the sub-blocks have some gaps (like 10px) inside, this block
+                // should use a larger gap (like 20px) to distinguish those sub-blocks.
+                if (subGapLevel >= thisGapLevelBetweenSubBlocks) {
+                    thisGapLevelBetweenSubBlocks = subGapLevel + (
+                        (
+                            thisBlockHasInnerGap && (
+                                // 0 always can not be readable gap level.
+                                !subGapLevel
+                                // If no header, always keep the sub gap level. Otherwise
+                                // look weird in case `multipleSeries`.
+                                || (subBlock.type === 'section' && !subBlock.noHeader)
+                            )
+                        ) ? 1 : 0
+                    );
+                }
+            });
+            fragment.__gapLevelBetweenSubBlocks = thisGapLevelBetweenSubBlocks;
+        },
+
+        build(ctx, fragment: TooltipMarkupSection, topMarginForOuterGap): string {
+            const noHeader = fragment.noHeader;
+            const gaps = getGap(fragment);
+
+            const subMarkupText = buildSubBlocks(
+                ctx,
+                fragment,
+                noHeader ? topMarginForOuterGap : gaps.html
+            );
+
+            if (noHeader) {
+                return subMarkupText;
+            }
+
+            const displayableHeader = makeValueReadable(fragment.header, 'ordinal');
+            if (ctx.renderMode === 'richText') {
+                return wrapInlineNameRichText(ctx, displayableHeader) + gaps.richText
+                    + subMarkupText;
+            }
+            else {
+                return wrapBlockHTML(
+                    `<div style="${TOOLTIP_NAME_TEXT_STYLE_CSS};${TOOLTIP_LINE_HEIGHT_CSS};">`
+                        + encodeHTML(displayableHeader)
+                        + '</div>'
+                        + subMarkupText,
+                    topMarginForOuterGap
+                );
+            }
+        }
+    },
+
+    /**
+     * A `nameValue` block is like:
+     * ```
+     * marker  name  value
+     * ```
+     */
+    nameValue: {
+        planLayout: function (fragment: TooltipMarkupNameValueBlock) {
+            fragment.__gapLevelBetweenSubBlocks = 0;
+        },
+
+        build(ctx, fragment: TooltipMarkupNameValueBlock, topMarginForOuterGap) {
+            const renderMode = ctx.renderMode;
+            const noName = fragment.noName;
+            const noValue = fragment.noValue;
+            const noMarker = !fragment.markerType;
+            const name = fragment.name;
+            const value = fragment.value;
+
+            if (noName && noValue) {
+                return;
+            }
+
+            const markerStr = noMarker
+                ? ''
+                : ctx.markupStyleCreator.makeTooltipMarker(
+                    fragment.markerType,
+                    fragment.markerColor || '#333',
+                    renderMode
+                );
+            const readableName = noName
+                ? ''
+                : makeValueReadable(name, 'ordinal');
+            const valueTypeOption = fragment.valueType;
+            const readableValueList = noValue
+                ? []
+                : (isArray(value)
+                    ? map(value, (val, idx) => makeValueReadable(
+                        val, isArray(valueTypeOption) ? valueTypeOption[idx] : valueTypeOption
+                    ))
+                    : [makeValueReadable(
+                        value, isArray(valueTypeOption) ? valueTypeOption[0] : valueTypeOption
+                    )]
+                );
+            const valueAlignRight = !noMarker || !noName;
+            // It little weird if only value next to marker but far from marker.
+            const valueCloseToMarker = !noMarker && noName;
+
+            return renderMode === 'richText'
+                ? (
+                    (noMarker ? '' : markerStr)
+                    + (noName ? '' : wrapInlineNameRichText(ctx, readableName))
+                    // Value has commas inside, so use ' ' as delimiter for multiple values.
+                    + (noValue ? '' : wrapInlineValueRichText(
+                        ctx, readableValueList, valueAlignRight, valueCloseToMarker
+                    ))
+                )
+                : wrapBlockHTML(
+                    (noMarker ? '' : markerStr)
+                    + (noName ? '' : wrapInlineNameHTML(readableName, !noMarker))
+                    + (noValue ? '' : wrapInlineValueHTML(
+                        readableValueList, valueAlignRight, valueCloseToMarker
+                    )),
+                    topMarginForOuterGap
+                );
+        }
+    }
+};
+
+
+function buildSubBlocks(
+    ctx: TooltipMarkupBuildContext,
+    fragment: TooltipMarkupSection,
+    topMarginForOuterGap: number
+): MarkupText {
+    const subMarkupTextList: string[] = [];
+    let subBlocks = fragment.blocks || [];
+    assert(!subBlocks || isArray(subBlocks));
+    subBlocks = subBlocks || [];
+
+    const orderMode = ctx.orderMode;
+    if (fragment.sortBlocks && orderMode) {
+        subBlocks = subBlocks.slice();
+        const orderMap = { valueAsc: 'asc', valueDesc: 'desc' } as const;
+        if (hasOwn(orderMap, orderMode)) {
+            const comparator = new SortOrderComparator(orderMap[orderMode as 'valueAsc' | 'valueDesc'], null);
+            subBlocks.sort((a, b) => comparator.evaluate(a.sortParam, b.sortParam));
+        }
+        // FIXME 'seriesDesc' necessary?
+        else if (orderMode === 'seriesDesc') {
+            subBlocks.reverse();
+        }
+    }
+
+    const gaps = getGap(fragment);
+    each(subBlocks, function (subBlock, idx) {
+        const subMarkupText = getBuilder(subBlock).build(
+            ctx,
+            subBlock,
+            idx > 0 ? gaps.html : 0
+        );
+        subMarkupText != null && subMarkupTextList.push(subMarkupText);
+    });
+
+    if (!subMarkupTextList.length) {
+        return;
+    }
+
+    return ctx.renderMode === 'richText'
+        ? subMarkupTextList.join(gaps.richText)
+        : wrapBlockHTML(
+            subMarkupTextList.join(''),
+            topMarginForOuterGap
+        );
+}
+
+interface TooltipMarkupBuildContext {
+    renderMode: TooltipRenderMode;
+    orderMode: TooltipOrderMode;
+    markupStyleCreator: TooltipMarkupStyleCreator;
+}
+
+/**
+ * @return markupText. null/undefined means no content.
+ */
+export function buildTooltipMarkup(
+    fragment: TooltipMarkupBlockFragment,
+    markupStyleCreator: TooltipMarkupStyleCreator,
+    renderMode: TooltipRenderMode,
+    orderMode: TooltipOrderMode
+): MarkupText {
+    if (!fragment) {
+        return;
+    }
+
+    const builder = getBuilder(fragment);
+    builder.planLayout(fragment);
+    const ctx: TooltipMarkupBuildContext = {
+        renderMode: renderMode,
+        orderMode: orderMode,
+        markupStyleCreator: markupStyleCreator
+    };
+    return builder.build(ctx, fragment, 0);
+}
+
+
+function getGap(fragment: TooltipMarkupBlock): {
+    html: number;
+    richText: string
+} {
+    const gapLevelBetweenSubBlocks = fragment.__gapLevelBetweenSubBlocks;
+    return {
+        html: HTML_GAPS[gapLevelBetweenSubBlocks],
+        richText: RICH_TEXT_GAPS[gapLevelBetweenSubBlocks]
+    };
+}
+
+function wrapBlockHTML(
+    encodedContent: string,
+    topGap: number
+): string {
+    const clearfix = '<div style="clear:both"></div>';
+    const marginCSS = `margin: ${topGap}px 0 0`;
+    return `<div style="${marginCSS};${TOOLTIP_LINE_HEIGHT_CSS};">`
+        + encodedContent + clearfix
+        + '</div>';
+}
+
+function wrapInlineNameHTML(name: string, leftHasMarker: boolean): string {
+    const marginCss = leftHasMarker ? 'margin-left:2px' : '';
+    return `<span style="${TOOLTIP_NAME_TEXT_STYLE_CSS};${marginCss}">`
+        + encodeHTML(name)
+        + '</span>';
+}
+
+function wrapInlineValueHTML(valueList: string[], alignRight: boolean, valueCloseToMarker: boolean): string {
+    // Do not too close to marker, considering there are multiple values separated by spaces.
+    const paddingStr = valueCloseToMarker ? '10px' : '20px';
+    const alignCSS = alignRight ? `float:right;margin-left:${paddingStr}` : '';
+    return (
+        `<span style="${alignCSS};${TOOLTIP_VALUE_TEXT_STYLE_CSS}">`
+        // Value has commas inside, so use '  ' as delimiter for multiple values.
+        + map(valueList, value => encodeHTML(value)).join('&nbsp;&nbsp;')
+        + '</span>'
+    );
+}
+
+function wrapInlineNameRichText(ctx: TooltipMarkupBuildContext, name: string): string {
+    return ctx.markupStyleCreator.wrapRichTextStyle(name, TOOLTIP_TEXT_STYLE_RICH);
+}
+
+function wrapInlineValueRichText(
+    ctx: TooltipMarkupBuildContext,
+    valueList: string[],
+    alignRight: boolean,
+    valueCloseToMarker: boolean
+): string {
+    const styles: Dictionary<unknown>[] = [TOOLTIP_VALUE_TEXT_STYLE_RICH];
+    const paddingLeft = valueCloseToMarker ? 10 : 20;
+    alignRight && styles.push({ padding: [0, 0, 0, paddingLeft], align: 'right' });
+    // Value has commas inside, so use '  ' as delimiter for multiple values.
+    return ctx.markupStyleCreator.wrapRichTextStyle(valueList.join('  '), styles);
+}
+
+
+export function retrieveVisualColorForTooltipMarker(
+    series: SeriesModel,
+    dataIndex: number
+): ColorString {
+    const style = series.getData().getItemVisual(dataIndex, 'style');
+    const color = style[series.visualDrawType];
+    return convertToColorString(color);
+}
+
+export function getPaddingFromTooltipModel(
+    model: Model<TooltipOption>,
+    renderMode: TooltipRenderMode
+): number | number[] {
+    const padding = model.get('padding');
+    return padding != null
+        ? padding
+        // We give slightly different to look pretty.
+        : renderMode === 'richText'
+        ? [8, 10]
+        : 10;
+}
+
+/**
+ * The major feature is generate styles for `renderMode: 'richText'`.
+ * But it also serves `renderMode: 'html'` to provide
+ * "renderMode-independent" API.
+ */
+export class TooltipMarkupStyleCreator {
+    readonly richTextStyles: Dictionary<Dictionary<unknown>> = {};
+
+    // Notice that "generate a style name" usuall happens repeatly when mouse moving and
+    // displaying a tooltip. So we put the `_nextStyleNameId` as a member of each creator
+    // rather than static shared by all creators (which will cause it increase to fast).
+    private _nextStyleNameId: number = getRandomIdBase();
+
+    private _generateStyleName() {
+        return '__EC_aUTo_' + this._nextStyleNameId++;
+    }
+
+    makeTooltipMarker(
+        markerType: TooltipMarkerType,
+        colorStr: ColorString,
+        renderMode: TooltipRenderMode
+    ): string {
+        const markerId = renderMode === 'richText'
+            ? this._generateStyleName()
+            : null;
+        const marker = getTooltipMarker({
+            color: colorStr,
+            type: markerType,
+            renderMode,
+            markerId: markerId
+        });
+        if (isString(marker)) {
+            return marker;
+        }
+        else {
+            if (__DEV__) {
+                assert(markerId);
+            }
+            this.richTextStyles[markerId] = marker.style;
+            return marker.content;
+        }
+    }
+
+    /**
+     * @usage
+     * ```ts
+     * const styledText = markupStyleCreator.wrapRichTextStyle([
+     *     // The styles will be auto merged.
+     *     {
+     *         fontSize: 12,
+     *         color: 'blue'
+     *     },
+     *     {
+     *         padding: 20
+     *     }
+     * ]);
+     * ```
+     */
+    wrapRichTextStyle(text: string, styles: Dictionary<unknown> | Dictionary<unknown>[]): string {
+        const finalStl = {};
+        if (isArray(styles)) {
+            each(styles, stl => extend(finalStl, stl));
+        }
+        else {
+            extend(finalStl, styles);
+        }
+        const styleName = this._generateStyleName();
+        this.richTextStyles[styleName] = finalStl;
+        return `{${styleName}|${text}}`;
+    }
+}
diff --git a/src/coord/radar/IndicatorAxis.ts b/src/coord/radar/IndicatorAxis.ts
index 116e166..f03b9a0 100644
--- a/src/coord/radar/IndicatorAxis.ts
+++ b/src/coord/radar/IndicatorAxis.ts
@@ -33,8 +33,6 @@ class IndicatorAxis extends Axis {
 
     model: AxisBaseModel<InnerIndicatorAxisOption>;
 
-    value?: number | string;
-
     constructor(dim: string, scale: Scale, radiusExtent?: [number, number]) {
         super(dim, scale, radiusExtent);
     }
diff --git a/src/model/Series.ts b/src/model/Series.ts
index b631208..a082994 100644
--- a/src/model/Series.ts
+++ b/src/model/Series.ts
@@ -19,21 +19,15 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
-import {
-    formatTime,
-    encodeHTML,
-    addCommas,
-    getTooltipMarker
-} from '../util/format';
 import * as modelUtil from '../util/model';
 import {
     DataHost, DimensionName, StageHandlerProgressParams,
-    SeriesOption, TooltipRenderMode, ZRColor, BoxLayoutOptionMixin,
-    ScaleDataValue, Dictionary, ColorString, OptionDataItemObject, SeriesDataType
+    SeriesOption, ZRColor, BoxLayoutOptionMixin,
+    ScaleDataValue, Dictionary, OptionDataItemObject, SeriesDataType
 } from '../util/types';
 import ComponentModel, { ComponentModelConstructor } from './Component';
 import {ColorPaletteMixin} from './mixin/colorPalette';
-import DataFormatMixin from '../model/mixin/dataFormat';
+import { DataFormatMixin } from '../model/mixin/dataFormat';
 import Model from '../model/Model';
 import {
     getLayoutParams,
@@ -41,7 +35,6 @@ import {
     fetchLayoutMode
 } from '../util/layout';
 import {createTask} from '../stream/task';
-import {retrieveRawValue} from '../data/helper/dataProvider';
 import GlobalModel from './Global';
 import { CoordinateSystem } from '../coord/CoordinateSystem';
 import { ExtendableConstructor, mountExtend, Constructor } from '../util/clazz';
@@ -49,11 +42,11 @@ import { PipelineContext, SeriesTaskContext, GeneralTask, OverallTask, SeriesTas
 import LegendVisualProvider from '../visual/LegendVisualProvider';
 import List from '../data/List';
 import Axis from '../coord/Axis';
-import { GradientObject } from 'zrender/src/graphic/Gradient';
 import type { BrushCommonSelectorsForSeries, BrushSelectableArea } from '../component/brush/selector';
 import makeStyleMapper from './mixin/makeStyleMapper';
 import { SourceManager } from '../data/helper/sourceManager';
 import { Source } from '../data/Source';
+import { defaultSeriesFormatTooltip } from '../component/tooltip/seriesFormatTooltip';
 
 const inner = modelUtil.makeInner<{
     data: List
@@ -399,7 +392,6 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis();
     }
 
-    // FIXME
     /**
      * Default tooltip formatter
      *
@@ -412,178 +404,18 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
      *        'richText' is used for rendering tooltip in rich text form, for those where
      *        DOM operation is not supported.
      * @return formatted tooltip with `html` and `markers`
+     *        Notice: The override method can also return string
      */
     formatTooltip(
         dataIndex: number,
         multipleSeries?: boolean,
-        dataType?: SeriesDataType,
-        renderMode?: TooltipRenderMode
-    ): {
-        html: string,
-        markers: Dictionary<ColorString>
-    } | string { // The override method can also return string
-
-        const series = this;
-        renderMode = renderMode || 'html';
-        const newLine = renderMode === 'html' ? '' : '\n';
-        const isRichText = renderMode === 'richText';
-        const markers: Dictionary<ColorString> = {};
-        let markerId = 0;
-
-        function formatArrayValue(value: any[]) {
-            // ??? TODO refactor these logic.
-            // check: category-no-encode-has-axis-data in dataset.html
-            const vertially = zrUtil.reduce(value, function (vertially, val, idx) {
-                const dimItem = data.getDimensionInfo(idx);
-                return vertially |= (dimItem && dimItem.tooltip !== false && dimItem.displayName != null) as any;
-            }, 0);
-
-            const result: string[] = [];
-
-            tooltipDims.length
-                ? zrUtil.each(tooltipDims, function (dim) {
-                    setEachItem(retrieveRawValue(data, dataIndex, dim), dim);
-                })
-                // By default, all dims is used on tooltip.
-                : zrUtil.each(value, setEachItem);
-
-            function setEachItem(val: any, dim: DimensionName | number): void {
-                const dimInfo = data.getDimensionInfo(dim);
-                // If `dimInfo.tooltip` is not set, show tooltip.
-                if (!dimInfo || dimInfo.otherDims.tooltip === false) {
-                    return;
-                }
-                const dimType = dimInfo.type;
-                const markName = 'sub' + series.seriesIndex + 'at' + markerId;
-                const dimHead = getTooltipMarker({
-                    color: colorStr,
-                    type: 'subItem',
-                    renderMode: renderMode,
-                    markerId: markName
-                });
-
-                const dimHeadStr = typeof dimHead === 'string' ? dimHead : dimHead.content;
-                const valStr = (vertially
-                        ? '<span style="font-size:12px;color:#6e7079;">'
-                            + dimHeadStr + encodeHTML(dimInfo.displayName || '-')
-                            + '</span>'
-                        : ''
-                    )
-                    // FIXME should not format time for raw data?
-                    + '<span style="float:right;margin-left:20px;color:#000;font-weight:900">'
-                    + encodeHTML(dimType === 'ordinal'
-                        ? val + ''
-                        : dimType === 'time'
-                        ? (multipleSeries ? '' : formatTime('yyyy/MM/dd hh:mm:ss', val))
-                        : addCommas(val)
-                    )
-                    + '</span>';
-                valStr && result.push(`<div style="margin: 11px 0 0;line-height:1;">${valStr}</div>`);
-
-                if (isRichText) {
-                    markers[markName] = colorStr;
-                    ++markerId;
-                }
-            }
-
-            const newLine = vertially ? (isRichText ? '\n' : '') : '';
-            const content = newLine + result.join(newLine || '');
-            return {
-                renderMode: renderMode,
-                content: content,
-                style: markers
-            };
-        }
-
-        function formatSingleValue(val: any) {
-            // return encodeHTML(addCommas(val));
-            return {
-                renderMode: renderMode,
-                content: encodeHTML(addCommas(val)),
-                style: markers
-            };
-        }
-
-        const data = this.getData();
-        const tooltipDims = data.mapDimensionsAll('defaultedTooltip');
-        const tooltipDimLen = tooltipDims.length;
-        const value = this.getRawValue(dataIndex) as any;
-        const isValueArr = zrUtil.isArray(value);
-
-        const style = data.getItemVisual(dataIndex, 'style');
-        const color = style[this.visualDrawType];
-        let colorStr: ColorString;
-        if (zrUtil.isString(color)) {
-            colorStr = color;
-        }
-        else if (color && (color as GradientObject).colorStops) {
-            colorStr = ((color as GradientObject).colorStops[0] || {}).color;
-        }
-        colorStr = colorStr || 'transparent';
-
-        // Complicated rule for pretty tooltip.
-        const formattedValue = (tooltipDimLen > 1 || (isValueArr && !tooltipDimLen))
-            ? formatArrayValue(value)
-            : tooltipDimLen
-            ? formatSingleValue(retrieveRawValue(data, dataIndex, tooltipDims[0]))
-            : formatSingleValue(isValueArr ? value[0] : value);
-        const content = isRichText
-            ? formattedValue.content
-            : (tooltipDimLen > 1 || (isValueArr && !tooltipDimLen))
-            ? '<div>'
-                + formattedValue.content + '</div>'
-            : '<span style="float:right;margin-left:20px;color:#464646;font-weight:bold">'
-                + formattedValue.content + '</span>';
-
-        const markName = series.seriesIndex + 'at' + markerId;
-        const colorEl = getTooltipMarker({
-            color: colorStr,
-            type: 'item',
-            renderMode,
-            markerId: markName
+        dataType?: SeriesDataType
+    ): ReturnType<DataFormatMixin['formatTooltip']> {
+        return defaultSeriesFormatTooltip({
+            series: this,
+            dataIndex: dataIndex,
+            multipleSeries: multipleSeries
         });
-        markers[markName] = colorStr;
-        ++markerId;
-
-        const name = data.getName(dataIndex);
-
-        let seriesName = this.name;
-        if (!modelUtil.isNameSpecified(this)) {
-            seriesName = '';
-        }
-        seriesName = seriesName
-            ? encodeHTML(seriesName) + (!multipleSeries ? newLine : ' ')
-            : '';
-
-        colorStr = zrUtil.isString(colorEl) ? colorEl : colorEl.content;
-        let html = '';
-        if (!isRichText) {
-            seriesName = seriesName
-                ? !multipleSeries
-                ? `<div style="font-size:12px;color:#6e7079;line-height:1;margin-top:-4px;">${seriesName}</div>`
-                : `<span style="font-size:12px;color:#6e7079;line-height:1">${seriesName}</span>`
-                : '';
-            html = !multipleSeries
-                ? seriesName + `<div style="margin: ${seriesName ? 8 : 0}px 0 0;line-height:1">`
-                    + colorStr
-                    + (name
-                        ? `<span style="font-size:12px;color:#6e7079;">${encodeHTML(name)}</span>${content}`
-                        : content
-                    ) + '</div>'
-                : `<div style="margin: 11px 0 0;line-height:1;">${colorStr}${seriesName}${content}</div>`;
-        }
-        else {
-            html = !multipleSeries
-                ? seriesName + (seriesName ? '\n' : '') + ''
-                    + colorStr
-                    + (name
-                        ? `${encodeHTML(name)}: ${content}`
-                        : content
-                    ) + ''
-                : `${colorStr}${seriesName}: ${content}`;
-        }
-
-        return {html, markers};
     }
 
     isAnimationEnabled(): boolean {
@@ -842,4 +674,5 @@ function getCurrentTask(seriesModel: SeriesModel): GeneralTask {
     }
 }
 
+
 export default SeriesModel;
diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts
index f1aa9b7..83b492f 100644
--- a/src/model/mixin/dataFormat.ts
+++ b/src/model/mixin/dataFormat.ts
@@ -23,20 +23,20 @@ import {formatTpl} from '../../util/format';
 import {
     DataHost,
     DisplayState,
-    TooltipRenderMode,
     CallbackDataParams,
     ColorString,
     ZRColor,
     OptionDataValue,
-    SeriesDataType,
-    TooltipOrderMode
+    SeriesDataType
 } from '../../util/types';
 import GlobalModel from '../Global';
+import { TooltipMarkupBlockFragment } from '../../component/tooltip/tooltipMarkup';
+import { makePrintable } from '../../util/log';
 
 const DIMENSION_LABEL_REG = /\{@(.+?)\}/g;
 
 
-interface DataFormatMixin extends DataHost {
+export interface DataFormatMixin extends DataHost {
     ecModel: GlobalModel;
     mainType: string;
     subType: string;
@@ -46,7 +46,7 @@ interface DataFormatMixin extends DataHost {
     animatedValue: OptionDataValue[];
 }
 
-class DataFormatMixin {
+export class DataFormatMixin {
 
     /**
      * Get params for formatter
@@ -166,22 +166,75 @@ class DataFormatMixin {
      * @param {number} dataIndex
      * @param {boolean} [multipleSeries=false]
      * @param {string} [dataType]
-     * @param {string} [renderMode='html'] valid values: 'html' and 'richText'.
-     *                                     'html' is used for rendering tooltip in extra DOM form, and the result
-     *                                     string is used as DOM HTML content.
-     *                                     'richText' is used for rendering tooltip in rich text form, for those where
-     *                                     DOM operation is not supported.
      */
     formatTooltip(
         dataIndex: number,
         multipleSeries?: boolean,
-        dataType?: string,
-        renderMode?: TooltipRenderMode,
-        order?: TooltipOrderMode
-    ): string | {html: string, markers: {[markName: string]: string}} {
+        dataType?: string
+    ): TooltipFormatResult {
         // Empty function
         return;
     }
 };
 
-export default DataFormatMixin;
+type TooltipFormatResult =
+    // If `string`, means `TooltipFormatResultLegacyObject['html']`
+    string
+    // | TooltipFormatResultLegacyObject
+    | TooltipMarkupBlockFragment;
+
+// PENDING: previously we accept this type when calling `formatTooltip`,
+// but guess little chance has been used outside. Do we need to backward
+// compat it?
+// type TooltipFormatResultLegacyObject = {
+//     // `html` means the markup language text, either in 'html' or 'richText'.
+//     // The name `html` is not appropriate becuase in 'richText' it is not a HTML
+//     // string. But still support it for backward compat.
+//     html: string;
+//     markers: Dictionary<ColorString>;
+// };
+
+/**
+ * For backward compat, normalize the return from `formatTooltip`.
+ */
+export function normalizeTooltipFormatResult(
+    result: TooltipFormatResult
+    // markersExisting: Dictionary<ColorString>
+): {
+    // If `markupFragment` exists, `markupText` should be ignored.
+    markupFragment: TooltipMarkupBlockFragment;
+    // Can be `null`/`undefined`, means no tooltip.
+    markupText: string;
+    // Merged with `markersExisting`.
+    // markers: Dictionary<ColorString>;
+} {
+    let markupText;
+    // let markers: Dictionary<ColorString>;
+    let markupFragment: TooltipMarkupBlockFragment;
+    if (zrUtil.isObject(result)) {
+        if ((result as TooltipMarkupBlockFragment).type) {
+            markupFragment = result as TooltipMarkupBlockFragment;
+        }
+        else {
+            if (__DEV__) {
+                console.warn('The return type of `formatTooltip` is not supported: ' + makePrintable(result));
+            }
+        }
+        // else {
+        //     markupText = (result as TooltipFormatResultLegacyObject).html;
+        //     markers = (result as TooltipFormatResultLegacyObject).markers;
+        //     if (markersExisting) {
+        //         markers = zrUtil.merge(markersExisting, markers);
+        //     }
+        // }
+    }
+    else {
+        markupText = result;
+    }
+
+    return {
+        markupText: markupText,
+        // markers: markers || markersExisting,
+        markupFragment: markupFragment
+    };
+}
diff --git a/src/util/format.ts b/src/util/format.ts
index 0298aa6..049ffb5 100644
--- a/src/util/format.ts
+++ b/src/util/format.ts
@@ -18,10 +18,12 @@
 */
 
 import * as zrUtil from 'zrender/src/core/util';
-import { parseDate, isNumeric } from './number';
-import { TooltipRenderMode, ColorString } from './types';
+import { parseDate, isNumeric, numericToNumber } from './number';
+import { TooltipRenderMode, ColorString, ZRColor, DimensionType } from './types';
 import { Dictionary } from 'zrender/src/core/types';
-import { format, pad } from './time';
+import { GradientObject } from 'zrender/src/graphic/Gradient';
+import { format as timeFormat, pad } from './time';
+import { deprecateReplaceLog } from './log';
 
 /**
  * Add a comma each three digit.
@@ -67,13 +69,58 @@ export function encodeHTML(source: string): string {
         });
 }
 
-export function concatTooltipHtml(html: string, value: unknown, dontEncodeHtml?: boolean): string {
-    return (dontEncodeHtml ? html : `<span style="font-size:12px;color:#6e7079;">${encodeHTML(html)}</span>`)
-            + (value ? '<span style="float:right;margin-left:20px;color:#464646;font-weight:900;font-size:14px;">' : '')
-            + encodeHTML(value as string)
-            + (value ? '</span>' : '');
+
+/**
+ * Make value user readable for tooltip and label.
+ * "User readable":
+ *     Try to not print programmer-specific text like NaN, Infinity, null, undefined.
+ *     Avoid to display an empty string, which users can not recognize there is
+ *     a value and it might look like a bug.
+ */
+export function makeValueReadable(
+    value: unknown,
+    valueType?: DimensionType
+): string {
+    const USER_READABLE_DEFUALT_TIME_PATTERN = 'yyyy-MM-dd hh:mm:ss';
+
+    function stringToUserReadable(str: string): string {
+        return (str && zrUtil.trim(str)) ? str : '-';
+    }
+    function isNumberUserReadable(num: number): boolean {
+        return !!(num != null && !isNaN(num) && isFinite(num));
+    }
+
+    const isTypeTime = valueType === 'time';
+    const isValueDate = value instanceof Date;
+    if (isTypeTime || isValueDate) {
+        const date = isTypeTime ? parseDate(value) : value;
+        if (!isNaN(+date)) {
+            // PENDING: add param `useUTC`?
+            return timeFormat(date, USER_READABLE_DEFUALT_TIME_PATTERN);
+        }
+        else if (isValueDate) {
+            return '-';
+        }
+        // In other cases, continue to try to display the value in the following code.
+    }
+
+    if (valueType === 'ordinal') {
+        return zrUtil.isStringSafe(value)
+            ? stringToUserReadable(value)
+            : zrUtil.isNumber(value)
+            ? (isNumberUserReadable(value) ? value + '' : '-')
+            : '-';
+    }
+    // By default.
+    const numericResult = numericToNumber(value);
+    return isNumberUserReadable(numericResult)
+        ? addCommas(numericResult)
+        : zrUtil.isStringSafe(value)
+        ? stringToUserReadable(value)
+        : '-';
 }
 
+
 const TPL_VAR_ALIAS = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
 
 const wrapVar = function (varName: string, seriesIdx?: number): string {
@@ -105,7 +152,7 @@ export function formatTpl(
     if (isTimeAxis) {
         const axisValue = paramsList[0].data[paramsList[0].axisIndex];
         const date = parseDate(axisValue);
-        return format(date, tpl);
+        return timeFormat(date, tpl);
     }
     else {
         const $vars = paramsList[0].$vars || [];
@@ -142,20 +189,18 @@ export function formatTplSimple(tpl: string, param: Dictionary<any>, encode?: bo
 interface RichTextTooltipMarker {
     renderMode: TooltipRenderMode;
     content: string;
-    style: {
-        color: ColorString
-        [key: string]: any
-    };
+    style: Dictionary<unknown>;
 }
 export type TooltipMarker = string | RichTextTooltipMarker;
+export type TooltipMarkerType = 'item' | 'subItem';
 interface GetTooltipMarkerOpt {
     color?: ColorString;
     extraCssText?: string;
     // By default: 'item'
-    type?: 'item' | 'subItem';
+    type?: TooltipMarkerType;
     renderMode?: TooltipRenderMode;
     // id name for marker. If only one marker is in a rich text, this can be omitted.
-    // By default: 'X'
+    // By default: 'markerX'
     markerId?: string;
 }
 // Only support color string
@@ -170,7 +215,6 @@ export function getTooltipMarker(inOpt: ColorString | GetTooltipMarkerOpt, extra
     const type = opt.type;
     extraCssText = opt.extraCssText;
     const renderMode = opt.renderMode || 'html';
-    const markerId = opt.markerId || 'X';
 
     if (!color) {
         return '';
@@ -187,13 +231,27 @@ export function getTooltipMarker(inOpt: ColorString | GetTooltipMarkerOpt, extra
             + encodeHTML(color) + ';' + (extraCssText || '') + '"></span>';
     }
     else {
-        // Space for rich element marker
+        // Should better not to auto generate style name by auto-increment number here.
+        // Because this util is usually called in tooltip formatter, which is probably
+        // called repeatly when mouse move and the auto-increment number increases fast.
+        // Users can make their own style name by theirselves, make it unique and readable.
+        const markerId = opt.markerId || 'markerX';
         return {
             renderMode: renderMode,
-            content: '{marker' + markerId + '|}  ',
-            style: {
-                color: color
-            }
+            content: '{' + markerId + '|}  ',
+            style: type === 'subItem'
+                ? {
+                    width: 4,
+                    height: 4,
+                    borderRadius: 2,
+                    backgroundColor: color
+                }
+                : {
+                    width: 10,
+                    height: 10,
+                    borderRadius: 5,
+                    backgroundColor: color
+                }
         };
     }
 }
@@ -209,7 +267,11 @@ export function getTooltipMarker(inOpt: ColorString | GetTooltipMarkerOpt, extra
  *           and `module:echarts/util/number#parseDate`.
  * @inner
  */
-export function formatTime(tpl: string, value: number | string | Date, isUTC?: boolean) {
+export function formatTime(tpl: string, value: unknown, isUTC?: boolean) {
+    if (__DEV__) {
+        deprecateReplaceLog('echarts.format.formatTime', 'echarts.time.format');
+    }
+
     if (tpl === 'week'
         || tpl === 'month'
         || tpl === 'quarter'
@@ -255,6 +317,21 @@ export function capitalFirst(str: string): string {
     return str ? str.charAt(0).toUpperCase() + str.substr(1) : str;
 }
 
+/**
+ * @return Never be null/undefined.
+ */
+export function convertToColorString(color: ZRColor, defaultColor?: ColorString): ColorString {
+    defaultColor = defaultColor || 'transparent';
+    return zrUtil.isString(color)
+        ? color
+        : zrUtil.isObject(color)
+        ? (
+            (color as GradientObject).colorStops
+            && ((color as GradientObject).colorStops[0] || {}).color
+            || defaultColor
+        )
+        : defaultColor;
+}
 
 export {truncateText} from 'zrender/src/graphic/helper/parseText';
 
diff --git a/src/util/log.ts b/src/util/log.ts
index cfb0bb9..4e443f6 100644
--- a/src/util/log.ts
+++ b/src/util/log.ts
@@ -77,7 +77,7 @@ export function consoleLog(...args: unknown[]) {
  * @param hintInfo anything about the current execution context to hint users.
  * @throws Error
  */
-export function makePrintable(...hintInfo: unknown[]) {
+export function makePrintable(...hintInfo: unknown[]): string {
     let msg = '';
 
     if (__DEV__) {
diff --git a/src/util/model.ts b/src/util/model.ts
index 83a615f..dffc341 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -47,7 +47,7 @@ import { Dictionary } from 'zrender/src/core/types';
 import SeriesModel from '../model/Series';
 import CartesianAxisModel from '../coord/cartesian/AxisModel';
 import GridModel from '../coord/cartesian/GridModel';
-import { isNumeric } from './number';
+import { isNumeric, getRandomIdBase } from './number';
 
 /**
  * Make the name displayable. But we should
@@ -706,8 +706,7 @@ export function makeInner<T, Host extends object>() {
         return (hostObj as any)[key] || ((hostObj as any)[key] = {});
     };
 }
-// A random start point.
-let innerUniqueIndex = Math.round(Math.random() * 5);
+let innerUniqueIndex = getRandomIdBase();
 
 /**
  * If string, e.g., 'geo', means {geoIndex: 0}.
diff --git a/src/util/number.ts b/src/util/number.ts
index 775d51a..865c4c7 100644
--- a/src/util/number.ts
+++ b/src/util/number.ts
@@ -565,3 +565,12 @@ export function numericToNumber(val: unknown): number {
 export function isNumeric(val: unknown): val is number {
     return !isNaN(numericToNumber(val));
 }
+
+/**
+ * Use random base to prevent users hard code depending on
+ * this auto generated marker id.
+ * @return An positive integer.
+ */
+export function getRandomIdBase(): number {
+    return Math.round(Math.random() * 9);
+}
diff --git a/src/util/time.ts b/src/util/time.ts
index f7de3ae..712e396 100644
--- a/src/util/time.ts
+++ b/src/util/time.ts
@@ -106,7 +106,7 @@ export function getDefaultFormatPrecisionOfInterval(timeUnit: PrimaryTimeUnit):
 }
 
 export function format(
-    time: Date | number, template: string, lang?: string | Model<LocaleOption>, isUTC?: boolean
+    time: unknown, template: string, lang?: string | Model<LocaleOption>, isUTC?: boolean
 ): string {
     const date = numberUtil.parseDate(time);
     const y = date[fullYearGetterName(isUTC)]();
diff --git a/src/util/types.ts b/src/util/types.ts
index 5ebee91..c33f0c7 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -27,7 +27,7 @@
 
 import Group from 'zrender/src/graphic/Group';
 import Element, {ElementEvent, ElementTextConfig} from 'zrender/src/Element';
-import DataFormatMixin from '../model/mixin/dataFormat';
+import { DataFormatMixin } from '../model/mixin/dataFormat';
 import GlobalModel from '../model/Global';
 import ExtensionAPI from '../ExtensionAPI';
 import SeriesModel from '../model/Series';
@@ -275,6 +275,12 @@ export interface LoadingEffect extends Element {
     resize: () => void;
 }
 
+/**
+ * 'html' is used for rendering tooltip in extra DOM form, and the result
+ * string is used as DOM HTML content.
+ * 'richText' is used for rendering tooltip in rich text form, for those where
+ * DOM operation is not supported.
+ */
 export type TooltipRenderMode = 'html' | 'richText';
 
 export type TooltipOrderMode = 'valueAsc' | 'valueDesc' | 'seriesAsc' | 'seriesDesc';
diff --git a/test/new-tooltip.html b/test/new-tooltip.html
index 4a03f61..9de9530 100644
--- a/test/new-tooltip.html
+++ b/test/new-tooltip.html
@@ -21,6 +21,7 @@ under the License.
 
 <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>
 </head>
@@ -30,67 +31,151 @@ under the License.
         margin: 0;
         padding: 0;
     }
+    .chart {
+        width: 100vw;
+        height: 40vh;
+        margin: 30px auto 0 auto;
+    }
+    #stateConsole {
+        position: fixed;
+        top: 5px;
+        left: 5px;
+        background: #333;
+        color: #eee;
+        font-size: 18px;
+        padding: 10px;
+        font-family: Monaco, monospace;
+        box-shadow: #000 0 0 5px;
+        z-index: 99999;
+    }
+    #stateConsole input {
+        margin-left: 15px;
+    }
+    #stateConsole .checked {
+        color: yellow;
+        font-size: 30px;
+    }
+    h1 {
+        margin-top: 40px;
+        margin-left: 10px;
+        font-size: 20px;
+        font-family: Arial, Helvetica, sans-serif;
+    }
 </style>
 
 <body>
-    <h1>Tooltip in Line Chart</h1>
-    <div id="main1" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+
+    <div id="stateConsole">
+        <span>tooltip.renderMode: </span>
+        <span id="stateConsoleSelect"></span>
+    </div>
+    <br>
+    <br>
+    <br>
+    <br>
+
+    <h1>Hover point, show item tooltip (with arrow in renderMode 'html')</h1>
+    <div id="main1" class="chart"></div>
     <h1>Tooltip in Radar Chart</h1>
-    <div id="main2" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main2" class="chart"></div>
     <h1>Tooltip in Pie Chart</h1>
-    <div id="main3" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main3" class="chart"></div>
     <h1>Tooltip in Sankey Chart</h1>
-    <div id="main4" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main4" class="chart"></div>
     <h1>Tooltip in Graph Chart</h1>
-    <div id="main5" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main5" class="chart"></div>
+    <h1>Simple 2 value axis scatter (trigger: 'item' | 'axis')</h1>
+    <div id="main6" class="chart"></div>
     <h1>Tooltip in Tree Chart</h1>
-    <div id="main7" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main7" class="chart"></div>
     <h1>Tooltip in Multiple Chart</h1>
-    <div id="main8" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main8" class="chart"></div>
     <h1>Tooltip in Line Chart axis Y is main axis</h1>
-    <div id="main9" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main9" class="chart"></div>
     <h1>Tooltip in Treemap Chart</h1>
-    <div id="main10" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main10" class="chart"></div>
     <h1>Tooltip in Bar Chart</h1>
-    <div id="main11" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
-    <h1>Tooltip in Stacked Line Chart</h1>
-    <div id="main12" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main11" class="chart"></div>
+    <h1>Tooltip in Stacked Line Chart (and legend)</h1>
+    <div id="main12" class="chart"></div>
     <h1>Tooltip in Scatter Line Chart</h1>
-    <div id="main13" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
-    <h1>Tooltip in Boxplot Chart</h1>
-    <div id="main14" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main13" class="chart"></div>
+    <h1>Tooltip both in Candlestick (show dims vertically) and Line</h1>
+    <div id="main14" class="chart"></div>
+    <h1>Tooltip in Map Series</h1>
+    <div id="main15" class="chart"></div>
     <h1>Tooltip in Continuous Heatmap Chart</h1>
-    <div id="main16" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main16" class="chart"></div>
     <h1>Tooltip in Piecewise Heatmap Chart</h1>
-    <div id="main17" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main17" class="chart"></div>
     <h1>Tooltip in Gradien Line Chart</h1>
-    <div id="main18" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main18" class="chart"></div>
     <h1>Tooltip in Multiple X Axis Line Chart</h1>
-    <div id="main19" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main19" class="chart"></div>
     <h1>Tooltip in Gradient Bar Chart</h1>
-    <div id="main20" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
-    <h1>Tooltip in Stacked Bar Chart</h1>
-    <div id="main21" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main20" class="chart"></div>
+    <h1>Tooltip in Stacked Bar Chart (check MarkPoint)</h1>
+    <div id="main21" class="chart"></div>
     <h1>Tooltip in Stacked Bar Chart Horizontal</h1>
-    <div id="main22" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main22" class="chart"></div>
     <h1>Tooltip in Stacked Line Chart Horizontal</h1>
-    <div id="main23" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main23" class="chart"></div>
     <h1>Tooltip order valueAsc</h1>
-    <div id="main24" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
-    <h1>Tooltip order valueDesc</h1>
-    <div id="main25" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main24" class="chart"></div>
+    <h1>'value' xAxis, valueDesc</h1>
+    <div id="main25" class="chart"></div>
     <h1>Tooltip order seriesDesc</h1>
-    <div id="main26" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main26" class="chart"></div>
     <h1>Tooltip order seriesAsc</h1>
-    <div id="main27" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
-    <h1>Tooltip Rich text in Radar</h1>
-    <div id="main28" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
-    <h1>Tooltip Rich text in Line and valueDesc</h1>
-    <div id="main29" style="width: 100vw;height:40vh;margin: 0 auto;"></div>
+    <div id="main27" class="chart"></div>
+    <h1>Tooltip Radar and valueAsc</h1>
+    <div id="main28" class="chart"></div>
+    <h1>Tooltip 'category' xAxis with number value and valueDesc</h1>
+    <div id="main29" class="chart"></div>
+    <h1>Tooltip Line 'time' xAxis</h1>
+    <div id="main30" class="chart"></div>
+    <h1>tooltip.formatter callback. Markers should be displayed.</h1>
+    <div id="main31" class="chart"></div>
+
 
     <script>
+
+        var CURRENT_RENDER_MODE;
+        function initStateConsole() {
+            var TOOLTIP_RENDER_MODE_KEY = '__EC_TEST_TOOLTIP_RENDER_MODE__';
+            var RENDER_MODE_LIST = ['html', 'richText'];
+
+            CURRENT_RENDER_MODE = window.localStorage.getItem(TOOLTIP_RENDER_MODE_KEY);
+            if (!CURRENT_RENDER_MODE) {
+                CURRENT_RENDER_MODE = RENDER_MODE_LIST[0];
+            }
+
+            var containerDom = document.getElementById('stateConsoleSelect');
+            var html = [];
+            for (var i = 0; i < RENDER_MODE_LIST.length; i++) {
+                var modeVal = RENDER_MODE_LIST[i];
+                var checkedAttr = CURRENT_RENDER_MODE === modeVal ? ' checked="checked" ' : '';
+                var checkedStyle = CURRENT_RENDER_MODE === modeVal ? ' class="checked" ' : '';
+                html.push(
+                    '<input type="radio" name="renderMode" ' + checkedAttr + ' value="' + modeVal + '">',
+                    '<label ' + checkedStyle + 'for="' + modeVal + '">' + modeVal + '</label>'
+                );
+            }
+            containerDom.innerHTML = html.join('');
+            containerDom.onclick = function (e) {
+                var target = e.target;
+                if (target.tagName === 'INPUT') {
+                    var newRenderMode = target.value;
+                    window.localStorage.setItem(TOOLTIP_RENDER_MODE_KEY, newRenderMode);
+                    location.reload();
+                }
+            };
+        }
+        initStateConsole();
+
+
         require([
-            'echarts'
+            'echarts', 'map/js/province/jiangsu'
         ], function (echarts) {
             function createChart(domId) {
                 // Make some of the html able to be commented.
@@ -102,6 +187,7 @@ under the License.
             var chart3 = createChart('main3');
             var chart4 = createChart('main4');
             var chart5 = createChart('main5');
+            var chart6 = createChart('main6');
             var chart7 = createChart('main7');
             var chart8 = createChart('main8');
             var chart9 = createChart('main9');
@@ -110,6 +196,7 @@ under the License.
             var chart12 = createChart('main12');
             var chart13 = createChart('main13');
             var chart14 = createChart('main14');
+            var chart15 = createChart('main15');
             var chart16 = createChart('main16');
             var chart17 = createChart('main17');
             var chart18 = createChart('main18');
@@ -124,9 +211,12 @@ under the License.
             var chart27 = createChart('main27');
             var chart28 = createChart('main28');
             var chart29 = createChart('main29');
+            var chart30 = createChart('main30');
+            var chart31 = createChart('main31');
 
             option1 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'item',
                     position: 'bottom',
                     axisPointer: {
@@ -167,10 +257,8 @@ under the License.
             };
 
             option2 = {
-                title: {
-                    text: '基础雷达图'
-                },
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     // attachToPoint: true
                 },
                 radar: {
@@ -227,6 +315,7 @@ under the License.
 
             option3 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'item',
                     // formatter: '{a} <br/>{b}: {c} ({d}%)'
                 },
@@ -274,7 +363,9 @@ under the License.
             };
 
             option4 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 series: {
                     type: 'sankey',
                     layout: 'none',
@@ -321,17 +412,16 @@ under the License.
             };
 
             option5 = {
-                title: {
-                    text: 'Graph 简单示例'
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                 },
-                tooltip: {},
                 animationDurationUpdate: 1500,
                 animationEasingUpdate: 'quinticInOut',
                 series: [{
                     type: 'graph',
                     layout: 'none',
                     symbolSize: 50,
-                    roam: true,
+                    roam: false,
                     label: {
                         show: true
                     },
@@ -402,8 +492,69 @@ under the License.
                 }]
             };
 
+            option6 = {
+                xAxis: {},
+                yAxis: {
+                    name: 'Should display multi-dim of an item in one line',
+                    nameTextStyle: {
+                        align: 'left'
+                    }
+                },
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                    trigger: 'axis'
+                },
+                series: [{
+                    type: 'scatter',
+                    encode: {
+                        tooltip: [0, 1],
+                        label: 2
+                    },
+                    label: {
+                        show: true,
+                        position: 'top'
+                    },
+                    data: [
+                        [121, 1222, 'trigger: axis\nshow 2 dim value\nheader: x value']
+                    ]
+                }, {
+                    type: 'scatter',
+                    encode: {
+                        tooltip: [1],
+                        label: [2]
+                    },
+                    label: {
+                        show: true,
+                        position: 'top'
+                    },
+                    data: [
+                        [121, 3444, 'trigger: axis\nshow 1 dim value\nheader: x value'],
+                        [666, 4122, 'trigger: axis\nshow 1 dim value\nheader: x value']
+                    ]
+                }, {
+                    type: 'scatter',
+                    encode: {
+                        tooltip: [0, 1],
+                        label: 2
+                    },
+                    tooltip: {
+                        trigger: 'item'
+                    },
+                    label: {
+                        show: true,
+                        position: 'top'
+                    },
+                    data: [
+                        [321, 0, 'trigger: item\nshow 2 dim value'],
+                        [466, 4122, 'trigger: item\nshow 2 dim value']
+                    ]
+                }]
+            };
+
             option7 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 series: [{
                     type: "tree",
                     data: [{
@@ -428,7 +579,7 @@ under the License.
                                 value: 25,
                             }]
                         }, {
-                            name: "Child B",
+                            name: "Child B (should no value)",
                             children: [{
                                 name: "Leaf G",
                                 value: 26,
@@ -1169,12 +1320,8 @@ under the License.
             });
 
             option8 = {
-                title: {
-                    text: '雨量流量关系图',
-                    subtext: '数据来自西安兰特水电测控技术有限公司',
-                    left: 'center'
-                },
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         animation: false
@@ -1725,6 +1872,7 @@ under the License.
 
             option9 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -1764,9 +1912,12 @@ under the License.
             };
 
             option10 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 series: [{
                     type: 'treemap',
+                    roam: false,
                     data: [{
                         name: 'nodeA', // First tree
                         value: 10,
@@ -1793,7 +1944,9 @@ under the License.
             };
 
             option11 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 dataset: {
                     source: [
                         ['product', '2015', '2016', '2017'],
@@ -1823,6 +1976,7 @@ under the License.
 
             option12 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -1832,6 +1986,9 @@ under the License.
                     }
                 },
                 legend: {
+                    tooltip: {
+                        show: true
+                    },
                     data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
                 },
                 grid: {
@@ -1853,17 +2010,6 @@ under the License.
                         type: 'line',
                         stack: '总量',
                         areaStyle: {},
-                        // markPoint: {
-                        //     data: [{
-                        //             type: 'max',
-                        //             name: '最大值'
-                        //         },
-                        //         {
-                        //             type: 'min',
-                        //             name: '最小值'
-                        //         }
-                        //     ]
-                        // },
                         data: [150, 232, 201, 154, 190, 330, 410]
                     },
                     {
@@ -1871,17 +2017,6 @@ under the License.
                         type: 'line',
                         stack: '总量',
                         areaStyle: {},
-                        // markPoint: {
-                        //     data: [{
-                        //             type: 'max',
-                        //             name: '最大值'
-                        //         },
-                        //         {
-                        //             type: 'min',
-                        //             name: '最小值'
-                        //         }
-                        //     ]
-                        // },
                         data: [320, 332, 301, 334, 390, 430, 320]
                     },
                     {
@@ -2010,6 +2145,7 @@ under the License.
                     }
                 ],
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     formatter: 'Group {a}: ({c})'
                 },
                 xAxis: [{
@@ -2090,24 +2226,66 @@ under the License.
             };
 
             option14 = {
-                tooltip: {},
+                tooltip: {
+                    trigger: 'axis',
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 xAxis: {
                     data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27']
                 },
                 yAxis: {},
                 series: [{
-                    type: 'k',
+                    type: 'candlestick',
                     data: [
                         [20, 30, 10, 35],
                         [40, 35, 30, 55],
                         [33, 38, 33, 40],
                         [40, 40, 32, 42]
                     ]
+                }, {
+                    type: 'line',
+                    data: [
+                        4, 5, 1, 3
+                    ]
+                }]
+            };
+
+            option15 = {
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
+                legend: {},
+                series: [{
+                    name: '<A>',
+                    type: 'map',
+                    mapType: '江苏',
+                    showLegendSymbol: true,
+                    selectedMap: {
+                        '南京市': true
+                    },
+                    data: [
+                        { name: '南京市', value: 100 },
+                        { name: '苏州市', value: null },
+                    ]
+                }, {
+                    name: '<B>',
+                    type: 'map',
+                    mapType: '江苏',
+                    showLegendSymbol: true,
+                    selectedMap: {
+                        '南京市': true
+                    },
+                    data: [
+                        { name: '南京市', value: 50 },
+                        { name: '苏州市', value: 30 },
+                    ]
                 }]
             };
 
             option16 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 xAxis: {
                     type: "category",
                     data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
@@ -2270,7 +2448,9 @@ under the License.
             }
 
             option17 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 xAxis: {
                     type: "category",
                     data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
@@ -2522,6 +2702,7 @@ under the License.
                     text: 'Gradient along the x axis'
                 }],
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis'
                 },
                 xAxis: [{
@@ -2564,13 +2745,13 @@ under the License.
                 color: colors,
 
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross'
                     }
                 },
                 legend: {
-                    data: ['2015 降水量', '2016 降水量']
                 },
                 grid: {
                     top: 70,
@@ -2614,8 +2795,8 @@ under the License.
                         axisPointer: {
                             label: {
                                 formatter: function (params) {
-                                    return '降水量  ' + params.value +
-                                        (params.seriesData.length ? ':' + params.seriesData[0]
+                                    return 'precipitation  ' + params.value +
+                                        (params.seriesData.length ? ': ' + params.seriesData[0]
                                             .data : '');
                                 }
                             }
@@ -2629,13 +2810,14 @@ under the License.
                     type: 'value'
                 }],
                 series: [{
-                        name: '2015 降水量',
+                        name: '2015 precipitation',
                         type: 'line',
                         xAxisIndex: 1,
                         smooth: true,
                         data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3]
                     },
                     {
+                        // Make the length of name different with the previous one.
                         name: '2016 降水量',
                         type: 'line',
                         smooth: true,
@@ -2658,7 +2840,9 @@ under the License.
             }
 
             option20 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 xAxis: {
                     data: dataAxis,
                     axisLabel: {
@@ -2748,72 +2932,91 @@ under the License.
 
             option21 = {
                 legend: {},
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 dataset: {
                     source: [
-                        ['product', '2012', '2013', '2014', '2015'],
+                        // ['product', '2012', '2013', '2014', '2015'],
                         ['Matcha Latte', 41.1, 130.4, 65.1, 53.3],
                         ['Milk Tea', 86.5, 92.1, 85.7, 83.1],
                         ['Cheese Cocoa', 124.1, 67.2, 79.5, 86.4]
-                    ]
+                    ],
+                    sourceHeader: false
                 },
-                xAxis: {
+                grid: [{
+                    right: '55%'
+                }, {
+                    left: '55%'
+                }],
+                xAxis: [{
                     type: 'category',
-                },
-                yAxis: {},
+                    axisLabel: { interval: 0 }
+                }, {
+                    gridIndex: 1
+                }],
+                yAxis: [{
+                }, {
+                    name: 'left markPoint no name, should align center',
+                    nameTextStyle: { align: 'left' },
+                    gridIndex: 1
+                }],
                 series: [{
-                        type: 'bar',
-                        seriesLayoutBy: 'row',
-                        stack: '1',
-                        markPoint: {
-                            data: [{
-                                    type: 'max',
-                                    name: '最大值'
-                                },
-                                {
-                                    type: 'min',
-                                    name: '最小值'
-                                }
-                            ]
-                        },
+                    type: 'bar',
+                    seriesLayoutBy: 'column',
+                    stack: '1',
+                    markPoint: {
+                        data: [{
+                                type: 'max',
+                                name: '最大值'
+                            },
+                            {
+                                type: 'min',
+                                name: '最小值'
+                            }
+                        ]
                     },
-                    {
-                        type: 'bar',
-                        seriesLayoutBy: 'row',
-                        stack: '1',
-                        markPoint: {
-                            data: [{
-                                    type: 'max',
-                                    name: '最大值'
-                                },
-                                {
-                                    type: 'min',
-                                    name: '最小值'
-                                }
-                            ]
-                        },
+                }, {
+                    type: 'bar',
+                    seriesLayoutBy: 'column',
+                    stack: '1',
+                    markPoint: {
+                        data: [{
+                                type: 'max',
+                                // test if no name
+                                // name: '最大值'
+                            },
+                            {
+                                type: 'min',
+                                name: '最小值'
+                            }
+                        ]
                     },
-                    {
-                        type: 'bar',
-                        seriesLayoutBy: 'row',
-                        stack: '1',
-                        markPoint: {
-                            data: [{
-                                    type: 'max',
-                                    name: '最大值'
-                                },
-                                {
-                                    type: 'min',
-                                    name: '最小值'
-                                }
-                            ]
-                        },
-                    }
-                ]
+                }, {
+                    name: 'series long long name',
+                    type: 'line',
+                    encode: { x: 1, y: 2 },
+                    markPoint: {
+                        data: [{
+                                type: 'max',
+                                // test if no name
+                                // name: '最大值'
+                            },
+                            {
+                                type: 'min',
+                                name: '最小值'
+                            }
+                        ]
+                    },
+                    xAxisIndex: 1,
+                    yAxisIndex: 1
+                }]
             }
 
             option22 = {
-                tooltip: {},
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                },
                 legend: {
                     data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
                 },
@@ -2831,60 +3034,56 @@ under the License.
                     data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
                 },
                 series: [{
-                        name: '直接访问',
-                        type: 'bar',
-                        stack: '总量',
-                        label: {
-                            show: true,
-                            position: 'insideRight'
-                        },
-                        data: [320, 302, 301, 334, 390, 330, 320]
+                    name: '直接访问',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        show: true,
+                        position: 'insideRight'
                     },
-                    {
-                        name: '邮件营销',
-                        type: 'bar',
-                        stack: '总量',
-                        label: {
-                            show: true,
-                            position: 'insideRight'
-                        },
-                        data: [120, 132, 101, 134, 90, 230, 210]
+                    data: [320, 302, 301, 334, 390, 330, 320]
+                }, {
+                    name: '邮件营销',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        show: true,
+                        position: 'insideRight'
                     },
-                    {
-                        name: '联盟广告',
-                        type: 'bar',
-                        stack: '总量',
-                        label: {
-                            show: true,
-                            position: 'insideRight'
-                        },
-                        data: [220, 182, 191, 234, 290, 330, 310]
+                    data: [120, 132, 101, 134, 90, 230, 210]
+                }, {
+                    name: '联盟广告',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        show: true,
+                        position: 'insideRight'
                     },
-                    {
-                        name: '视频广告',
-                        type: 'bar',
-                        stack: '总量',
-                        label: {
-                            show: true,
-                            position: 'insideRight'
-                        },
-                        data: [150, 212, 201, 154, 190, 330, 410]
+                    data: [220, 182, 191, 234, 290, 330, 310]
+                }, {
+                    name: '视频广告',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        show: true,
+                        position: 'insideRight'
                     },
-                    {
-                        name: '搜索引擎',
-                        type: 'bar',
-                        stack: '总量',
-                        label: {
-                            show: true,
-                            position: 'insideRight'
-                        },
-                        data: [820, 832, 901, 934, 1290, 1330, 1320]
-                    }
-                ]
+                    data: [150, 212, 201, 154, 190, 330, 410]
+                }, {
+                    name: '搜索引擎',
+                    type: 'bar',
+                    stack: '总量',
+                    label: {
+                        show: true,
+                        position: 'insideRight'
+                    },
+                    data: [820, 832, 901, 934, 1290, 1330, 1320]
+                }]
             };
 
             option23 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -2911,42 +3110,29 @@ under the License.
                     type: 'value'
                 }],
                 series: [{
-                        name: '视频广告',
-                        type: 'line',
-                        stack: '总量',
-                        areaStyle: {},
-                        data: [150, 232, 201, 154, 190, 330, 410]
-                    },
-                    {
-                        name: '直接访问',
-                        type: 'line',
-                        stack: '总量',
-                        areaStyle: {},
-                        data: [320, 332, 301, 334, 390, 430, 320]
-                    },
-                    {
-                        name: '搜索引擎',
-                        type: 'line',
-                        stack: '总量',
-                        areaStyle: {},
-                        markPoint: {
-                            data: [{
-                                    type: 'max',
-                                    name: '最大值'
-                                },
-                                {
-                                    type: 'min',
-                                    name: '最小值'
-                                }
-                            ]
-                        },
-                        data: [820, 932, 901, 934, 1290, 1330, 1320]
-                    }
-                ]
+                    name: '视频广告',
+                    type: 'line',
+                    stack: '总量',
+                    areaStyle: {},
+                    data: [150, 232, 201, 154, 190, 330, 410]
+                }, {
+                    name: '直接访问',
+                    type: 'line',
+                    stack: '总量',
+                    areaStyle: {},
+                    data: [320, 332, 301, 334, 390, 430, 320]
+                }, {
+                    name: '搜索引擎',
+                    type: 'line',
+                    stack: '总量',
+                    areaStyle: {},
+                    data: [820, 932, 901, 934, 1290, 1330, 1320]
+                }]
             };
 
             option24 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -2995,6 +3181,7 @@ under the License.
 
             option25 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -3005,44 +3192,49 @@ under the License.
                     order: 'valueDesc'
                 },
                 xAxis: [{
-                    type: 'category',
-                    boundaryGap: false,
-                    data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
                 }],
                 yAxis: [{
-                    type: 'value'
                 }],
-                series: [
-                    {
-                        name: '邮件营销',
-                        type: 'line',
-                        data: [120, 132, 101, 134, 90, 230, 210]
-                    },
-                    {
-                        name: '联盟广告',
-                        type: 'line',
-                        data: [220, 182, 191, 234, 290, 630, 310]
-                    },
-                    {
-                        name: '视频广告',
-                        type: 'line',
-                        data: [150, 232, 201, 154, 190, 430, 410]
-                    },
-                    {
-                        name: '直接访问',
-                        type: 'line',
-                        data: [320, 332, 301, 334, 390, 330, 320]
-                    },
-                    {
-                        name: '搜索引擎',
-                        type: 'line',
-                        data: [820, 932, 901, 934, 1290, 1330, 1320]
-                    }
-                ]
+                legend: {},
+                series: [{
+                    name: 'AA',
+                    type: 'scatter',
+                    encode: { tooltip: [1, 2] },
+                    data: [
+                        [1903, 15, 0.332],
+                        [2277, 231, 0.142],
+                        [1000, 89, 1.552],
+                        [2000, 194, 0.998],
+                        [1800, 105, 1.291]
+                    ]
+                }, {
+                    name: 'BB',
+                    type: 'scatter',
+                    encode: { tooltip: [1, 2] },
+                    data: [
+                        [1903, 99, 0.552],
+                        [2277, 23, 0.981],
+                        [1000, 98, 1.455],
+                        [2000, 71, 0.311],
+                        [1800, 157, 0.891]
+                    ]
+                }, {
+                    name: 'CC',
+                    type: 'scatter',
+                    encode: { tooltip: [1, 2] },
+                    data: [
+                        [1903, 81, 0.111],
+                        [2277, 71, 1.000],
+                        [1000, 125, 0.998],
+                        [2000, 20, 0.512],
+                        [1800, 78, 1.001]
+                    ]
+                }]
             };
 
             option26 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -3052,6 +3244,7 @@ under the License.
                     },
                     order: 'seriesDesc'
                 },
+                legend: {},
                 xAxis: [{
                     type: 'category',
                     boundaryGap: false,
@@ -3063,27 +3256,32 @@ under the License.
                 series: [
                     {
                         name: '邮件营销',
-                        type: 'line',
+                        type: 'bar',
+                        stack: 'a',
                         data: [120, 132, 101, 134, 90, 230, 210]
                     },
                     {
                         name: '联盟广告',
-                        type: 'line',
+                        type: 'bar',
+                        stack: 'a',
                         data: [220, 182, 191, 234, 290, 630, 310]
                     },
                     {
                         name: '视频广告',
-                        type: 'line',
+                        type: 'bar',
+                        stack: 'a',
                         data: [150, 232, 201, 154, 190, 430, 410]
                     },
                     {
                         name: '直接访问',
-                        type: 'line',
+                        type: 'bar',
+                        stack: 'a',
                         data: [320, 332, 301, 334, 390, 330, 320]
                     },
                     {
                         name: '搜索引擎',
-                        type: 'line',
+                        type: 'bar',
+                        stack: 'a',
                         data: [820, 932, 901, 934, 1290, 1330, 1320]
                     }
                 ]
@@ -3091,6 +3289,7 @@ under the License.
 
             option27 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     axisPointer: {
                         type: 'cross',
@@ -3100,6 +3299,7 @@ under the License.
                     },
                     order: 'seriesAsc'
                 },
+                legend: {},
                 xAxis: [{
                     type: 'category',
                     boundaryGap: false,
@@ -3112,39 +3312,46 @@ under the License.
                     {
                         name: '邮件营销',
                         type: 'line',
+                        stack: 'a',
+                        areaStyle: {},
                         data: [120, 132, 101, 134, 90, 230, 210]
                     },
                     {
                         name: '联盟广告',
                         type: 'line',
+                        stack: 'a',
+                        areaStyle: {},
                         data: [220, 182, 191, 234, 290, 630, 310]
                     },
                     {
                         name: '视频广告',
                         type: 'line',
+                        stack: 'a',
+                        areaStyle: {},
                         data: [150, 232, 201, 154, 190, 430, 410]
                     },
                     {
                         name: '直接访问',
                         type: 'line',
+                        stack: 'a',
+                        areaStyle: {},
                         data: [320, 332, 301, 334, 390, 330, 320]
                     },
                     {
                         name: '搜索引擎',
                         type: 'line',
+                        stack: 'a',
+                        areaStyle: {},
                         data: [820, 932, 901, 934, 1290, 1330, 1320]
                     }
                 ]
             };
 
             option28 = {
-                title: {
-                    text: '基础雷达图'
-                },
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     order: 'valueAsc',
                     // position: 'top',
-                    renderMode: 'richText'
                 },
                 radar: {
                     // shape: 'circle',
@@ -3200,6 +3407,7 @@ under the License.
 
             option29 = {
                 tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
                     trigger: 'axis',
                     position: 'top',
                     axisPointer: {
@@ -3209,12 +3417,12 @@ under the License.
                         }
                     },
                     order: 'valueDesc',
-                    renderMode: 'richText'
                 },
+                legend: {},
                 xAxis: [{
                     type: 'category',
                     boundaryGap: false,
-                    data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
+                    data: [2001, 2002, 2003, 2004, 2005, 2006, 2007]
                 }],
                 yAxis: [{
                     type: 'value'
@@ -3248,11 +3456,113 @@ under the License.
                 ]
             };
 
+            option30 = {
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                    trigger: 'axis',
+                    position: 'top',
+                    axisPointer: {
+                        type: 'cross',
+                        label: {
+                            backgroundColor: '#6a7985'
+                        }
+                    }
+                },
+                xAxis: [{
+                    type: 'time'
+                }],
+                yAxis: [{
+                }],
+                series: [
+                    {
+                        type: 'line',
+                        data: [
+                            ['2012-05-19', 120],
+                            [new Date(2012, 4, 20), 132],
+                            [+new Date(2012, 4, 21), 101],
+                            ['2012-05-22 00:00:00', 134],
+                            ['2012-05-23T00:00:00', 90]
+                        ]
+                    },
+                    {
+                        type: 'line',
+                        data: [
+                            ['2012-05-19', 220],
+                            ['2012-05-21', 182],
+                            ['2012-05-22', 191],
+                            ['2012-05-23', 234],
+                            ['2012-05-24', 290]
+                        ]
+                    }
+                ]
+            };
+
+            option31 = {
+                tooltip: {
+                    renderMode: CURRENT_RENDER_MODE,
+                    trigger: 'axis',
+                    position: 'top',
+                    axisPointer: {
+                        type: 'cross',
+                        label: {
+                            backgroundColor: '#6a7985'
+                        }
+                    },
+                    formatter: function (params) {
+                        if (CURRENT_RENDER_MODE === 'richText') {
+                            var content = ['Formatter Result:'];
+                            for (var i = 0; i < params.length; i++) {
+                                var param = params[i];
+                                content.push(param.marker + ' ' + param.value.join(' '));
+                            }
+                            return content.join('\n');
+                        }
+                        else {
+                            var content = ['<div>Formatter Result:</div>'];
+                            for (var i = 0; i < params.length; i++) {
+                                var param = params[i];
+                                content.push('<div>' + param.marker + param.value.join(' ') + '</div>');
+                            }
+                            return content.join('');
+                        }
+                    }
+                },
+                xAxis: [{
+                    type: 'time'
+                }],
+                yAxis: [{
+                }],
+                series: [
+                    {
+                        type: 'line',
+                        data: [
+                            ['2012-05-19', 120],
+                            [new Date(2012, 4, 20), 132],
+                            [+new Date(2012, 4, 21), 101],
+                            ['2012-05-22 00:00:00', 134],
+                            ['2012-05-23T00:00:00', 90]
+                        ]
+                    },
+                    {
+                        type: 'line',
+                        data: [
+                            ['2012-05-19', 220],
+                            ['2012-05-21', 182],
+                            ['2012-05-22', 191],
+                            ['2012-05-23', 234],
+                            ['2012-05-24', 290]
+                        ]
+                    }
+                ]
+            };
+
+
             chart1 && chart1.setOption(option1);
             chart2 && chart2.setOption(option2);
             chart3 && chart3.setOption(option3);
             chart4 && chart4.setOption(option4);
             chart5 && chart5.setOption(option5);
+            chart6 && chart6.setOption(option6);
             chart7 && chart7.setOption(option7);
             chart8 && chart8.setOption(option8);
             chart9 && chart9.setOption(option9);
@@ -3261,6 +3571,7 @@ under the License.
             chart12 && chart12.setOption(option12);
             chart13 && chart13.setOption(option13);
             chart14 && chart14.setOption(option14);
+            chart15 && chart15.setOption(option15);
             chart16 && chart16.setOption(option16);
             chart17 && chart17.setOption(option17);
             chart18 && chart18.setOption(option18);
@@ -3275,6 +3586,8 @@ under the License.
             chart27 && chart27.setOption(option27);
             chart28 && chart28.setOption(option28);
             chart29 && chart29.setOption(option29);
+            chart30 && chart30.setOption(option30);
+            chart31 && chart31.setOption(option31);
 
 
         });


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