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:41 UTC

[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 [...]

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