You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by GitBox <gi...@apache.org> on 2022/08/04 07:37:11 UTC

[GitHub] [echarts] Lruler opened a new pull request, #17471: [feat]: add thumbnails components

Lruler opened a new pull request, #17471:
URL: https://github.com/apache/echarts/pull/17471

   <!-- Please fill in the following information to help us review your PR more efficiently. -->
   
   ## Brief Information
   
   This pull request is in the type of:
   
   - [ ] bug fixing
   - [x] new feature
   - [ ] others
   
   
   
   ### What does this PR do?
   add thumbnails components
   
   
   
   ### Fixed issues
   [#7521](https://github.com/apache/echarts/issues/7521)& [#11352](https://github.com/apache/echarts/issues/11352)
   
   <!--
   - #xxxx: ...
   -->
   
   
   ## Details
   
   ### Before: What was the problem?
   
   <!-- DESCRIBE THE BUG OR REQUIREMENT HERE. -->
   
   <!-- ADD SCREENSHOT HERE IF APPLICABLE. -->
   
   
   
   ### After: How does it behave after the fixing?
   
   <!-- THE RESULT AFTER FIXING AND A SIMPLE EXPLANATION ABOUT HOW IT IS FIXED. -->
   
   <!-- ADD SCREENSHOT HERE IF APPLICABLE. -->
   
   
   
   ## Document Info
   
   One of the following should be checked.
   
   - [ ] This PR doesn't relate to document changes
   - [ ] The document should be updated later
   - [ ] The document changes have been made in apache/echarts-doc#xxx
   
   
   
   ## Misc
   
   ### ZRender Changes
   
   - [ ] This PR depends on ZRender changes (ecomfe/zrender#xxx).
   
   ### Related test cases or examples to use the new APIs
   
   N.A.
   
   
   
   ## Others
   
   ### Merging options
   
   - [ ] Please squash the commits into a single one when merging.
   
   ### Other information
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r996430747


##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,
+                        width: 5,
+                        height: 5,
+                        x: (x * 0.25 - 2.5),
+                        y: (y * 0.25 - 2.5)
+                      },
+                  style: (sub as graphic.Path).style,
+                  z: 3,
+                  z2: 151
+              });
+
+              thumbnailGroup.add(subThumbnail);
+        }
+
+      for (const node of lineNodes) {
+          const line = (node as graphic.Group).children()[0];
+          const lineThumbnail = new ECLinePath({
+              style: (line as ECLinePath).style,
+              shape: (line as ECLinePath).shape,
+              scaleX: 0.25,
+              scaleY: 0.25,
+              z: 3,

Review Comment:
   Just remove it



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] 100pah commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
100pah commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r1009111834


##########
src/chart/graph/GraphSeries.ts:
##########
@@ -509,6 +517,28 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
             itemStyle: {
                 borderColor: '#212121'
             }
+        },
+
+        thumbnail: {
+            show: false,
+
+            right: 0,
+            bottom: 0,
+
+            height: '25%',
+            width: '25%',

Review Comment:
   The default value '25%' probably too big.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] plainheart commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
plainheart commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r998966672


##########
src/chart/graph/GraphSeries.ts:
##########
@@ -54,6 +54,8 @@ import { LineDataVisual } from '../../visual/commonVisualTypes';
 import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
 import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip';
 import {initCurvenessList, createEdgeMapForCurveness} from '../helper/multipleGraphEdgeHelper';
+import { Color } from '../../echarts.all';

Review Comment:
   `Color` got the wrong import. It might be `ZRColor` from `util/types`.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r1000738928


##########
src/chart/graph/GraphView.ts:
##########
@@ -312,6 +335,13 @@ class GraphView extends ChartView {
         this._symbolDraw && this._symbolDraw.remove();
         this._lineDraw && this._lineDraw.remove();
     }
+
+    private _renderThumbnail(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        if (this._thumbanil) {

Review Comment:
   Use `this.group.remove(this._thumbnail.group)` to remove the whole group



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,242 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _thumbnailSystem: graphic.Rect;
+
+    _wrapper: graphic.Rect;
+
+    _height: number;
+    _width: number;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const itemStyle = model.getModel('itemStyle');
+        this._height = model.getModel('height').option;

Review Comment:
   use `model.get('height')`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r996404866


##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,
+                        width: 5,
+                        height: 5,
+                        x: (x * 0.25 - 2.5),
+                        y: (y * 0.25 - 2.5)
+                      },
+                  style: (sub as graphic.Path).style,
+                  z: 3,
+                  z2: 151
+              });
+
+              thumbnailGroup.add(subThumbnail);
+        }
+
+      for (const node of lineNodes) {
+          const line = (node as graphic.Group).children()[0];
+          const lineThumbnail = new ECLinePath({
+              style: (line as ECLinePath).style,
+              shape: (line as ECLinePath).shape,
+              scaleX: 0.25,
+              scaleY: 0.25,
+              z: 3,
+              z2: 151
+          });
+          thumbnailGroup.add(lineThumbnail);
+      }
+
+      const thumbnailWrapper = new graphic.Rect({
+          style: {
+              stroke: borderColor.option,
+              fill: backgroundColor.option,
+              lineWidth: 2
+          },
+          shape: {
+              height: thumbnailHeight === 0 ? layoutParams.box.height / 4 : thumbnailHeight,
+              width: thumbnailWidth === 0 ? layoutParams.box.width / 4 : thumbnailWidth
+          },
+          z: 2,
+          z2: 130
+      });
+
+      this._wrapper = thumbnailWrapper;
+
+      const areaStyle = selectedDataBackground.get('areaStyle');
+      const lineStyle = selectedDataBackground.get('lineStyle');
+
+      this._selectedRect = new graphic.Rect({
+          style: {...areaStyle, ...lineStyle},
+          shape: clone(thumbnailWrapper.shape),
+          z: 2,
+          z2: 150,
+          draggable: true,
+          ignore: true
+      });
+
+      group.add(thumbnailWrapper);
+      group.add(thumbnailGroup);
+      group.add(this._selectedRect);
+
+      layout.positionElement(group, layoutParams.pos, layoutParams.box);
+  }
+
+    remove() {
+        this.group.removeAll();
+    }
+
+    _updateZoom(e: RoamEventParams['zoom']) {
+
+        const wrapper = this._wrapper.getBoundingRect();
+        const {height, width} = this._layoutParams.box;
+        const rect = this._selectedRect;
+        const origin = [0, 0];
+        const end = [width, height];
+        const originData = this._treeModel.coordinateSystem.pointToData(origin);
+        const endData = this._treeModel.coordinateSystem.pointToData(end);
+        const x0 = (originData as number[])[0] / width;
+        const x1 = (endData as number[])[0] / width;
+
+        const y0 = (originData as number[])[1] / height;
+        const y1 = (endData as number[])[1] / height;
+
+        const offectX = x0 * rect.shape.width;
+        const offectY = y0 * rect.shape.height;
+
+        if (x1 - x0 >= 1 || !wrapper.contain(offectX, offectY)) {
+            this._selectedRect.hide();
+            return;
+        }
+
+        if (x1 - x0 < 0.08) {
+            return;
+        };
+        if (this._selectedRect.ignore) {
+            this._selectedRect.show();

Review Comment:
   No need to check `ignore` here. Always call `show` has no other side effects.



##########
src/chart/graph/GraphView.ts:
##########
@@ -287,6 +293,7 @@ class GraphView extends ChartView {
                 this._lineDraw.updateLayout();
                 // Only update label layout on zoom
                 api.updateLabelLayout();
+                this._thumbanil._updateZoom(e);

Review Comment:
   No need to separate `zoom` and `pan` update. We need to always recaculate the selected rect whenever `zoom`, `pan` or `re-render`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r996550379


##########
src/chart/graph/GraphView.ts:
##########
@@ -60,18 +61,23 @@ class GraphView extends ChartView {
 
     private _layouting: boolean;
 
+    private _thumbanil: Thumbnail;

Review Comment:
   typo here. should be thumbnail



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r998136168


##########
src/chart/graph/GraphSeries.ts:
##########
@@ -509,6 +533,25 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
             itemStyle: {
                 borderColor: '#212121'
             }
+        },
+
+        thumbnail: {
+            show: true,

Review Comment:
   Should default to be `false`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] Lruler commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
Lruler commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r996430240


##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,
+                        width: 5,
+                        height: 5,
+                        x: (x * 0.25 - 2.5),
+                        y: (y * 0.25 - 2.5)
+                      },
+                  style: (sub as graphic.Path).style,
+                  z: 3,
+                  z2: 151
+              });
+
+              thumbnailGroup.add(subThumbnail);
+        }
+
+      for (const node of lineNodes) {
+          const line = (node as graphic.Group).children()[0];
+          const lineThumbnail = new ECLinePath({
+              style: (line as ECLinePath).style,
+              shape: (line as ECLinePath).shape,
+              scaleX: 0.25,
+              scaleY: 0.25,
+              z: 3,

Review Comment:
   sorry, I don't understand how to use `z` in this link



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] Lruler commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
Lruler commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1295813198

   > Some remaining feature defects:
   > 
   > 1. The select box will be overflow the thumbnail container.
   > 
   > <img alt="image" width="440" src="https://user-images.githubusercontent.com/1956569/198828110-d5469a42-1512-449c-b62b-703e0ca2c3cd.png">
   > 
   > 2. When the select box is overflow the thumbnail, it disappears. Keeping display is expected.
   > 
   > Some features are required for a releasable product (IMO):
   > 
   > 1. The select box can be draggable.
   > 2. Mouse wheel zoom can be performed according to the thumbnail area when mouse hover it, that is, the zoom center is the point on thumbnail are, rather than the graph view.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] Lruler commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
Lruler commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1295815852

   > Some remaining feature defects:
   > 
   > 1. The select box will be overflow the thumbnail container.
   > 2. When the select box is overflow the thumbnail, it disappears. Keeping display is expected.
   
   I have handled this kind of situation in the code, but I don't know why it doesn't work in the zoom event, maybe it's a problem with triggering the frequency?
   ![image](https://user-images.githubusercontent.com/75160088/198829980-27810f73-b3be-46d4-9789-cd60dbf11021.png)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] echarts-bot[bot] commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
echarts-bot[bot] commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1206102178

   Document changes are required in this PR. Please also make a PR to [apache/echarts-doc](https://github.com/apache/echarts-doc) for document changes and update the issue id in the PR description. When the doc PR is merged, the maintainers will remove the `PR: awaiting doc` label.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r996549267


##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,193 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+import { zrUtil } from '../../echarts.all';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailScale = model.getModel('scale').option;
+        const selectedDataBackground = model.getModel('selectedDataBackground').option;
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, { width: 5,
+                height: 5,
+                x: (x * thumbnailScale - 2.5),
+                y: (y * thumbnailScale - 2.5)});
+
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+                });
+
+            thumbnailGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                scaleX: thumbnailScale,
+                scaleY: thumbnailScale,
+                z2: 151
+            });
+            thumbnailGroup.add(lineThumbnail);
+        }
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor.option,
+                fill: backgroundColor.option,
+                lineWidth: 2
+            },
+            shape: {
+                height: layoutParams.box.height * thumbnailScale,
+                width: layoutParams.box.width * thumbnailScale
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        const selectStyle = zrUtil.extend({lineWidth: 10, stroke: 'black'}, selectedDataBackground);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            shape: clone(thumbnailWrapper.shape),
+            ignore: true,
+            z2: 150

Review Comment:
   Why the selectedRect has smaller z2 than the edges



##########
src/chart/graph/GraphView.ts:
##########
@@ -60,18 +61,23 @@ class GraphView extends ChartView {
 
     private _layouting: boolean;
 
+    private _thumbanil: Thumbnail;
+
+    private _scale = 0;
+
     init(ecModel: GlobalModel, api: ExtensionAPI) {
         const symbolDraw = new SymbolDraw();
         const lineDraw = new LineDraw();
         const group = this.group;
-
+        const roamGroup = new graphic.Group();
         this._controller = new RoamController(api.getZr());
         this._controllerHost = {
-            target: group
+            target: roamGroup
         } as RoamControllerHost;
 
-        group.add(symbolDraw.group);
-        group.add(lineDraw.group);
+        roamGroup.add(symbolDraw.group);
+        roamGroup.add(lineDraw.group);
+        group.add(roamGroup);

Review Comment:
   I will prefer another name than `roamGroup`.



##########
src/chart/graph/GraphView.ts:
##########
@@ -312,6 +329,10 @@ class GraphView extends ChartView {
         this._symbolDraw && this._symbolDraw.remove();
         this._lineDraw && this._lineDraw.remove();
     }
+
+    private _renderThumbnail(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        this._thumbanil || (this._thumbanil = new Thumbnail(this.group)).render(seriesModel, api);

Review Comment:
   The `render` will not be called if `this._thumbnail` already exists. 



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -229,6 +232,28 @@ export interface GraphSeriesOption
      * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set
      */
     autoCurveness?: boolean | number | number[]
+
+    thumbnail?: {
+        show?: boolean,
+
+        top?: number | string,
+
+        bottom?: number | string,
+
+        left?: number | string,
+
+        right?: number | string,
+
+        scale?: number,

Review Comment:
   `scale` is not necessary?



##########
src/chart/graph/GraphView.ts:
##########
@@ -60,18 +61,23 @@ class GraphView extends ChartView {
 
     private _layouting: boolean;
 
+    private _thumbanil: Thumbnail;

Review Comment:
   type here. should be thumbnail



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,193 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+import { zrUtil } from '../../echarts.all';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailScale = model.getModel('scale').option;
+        const selectedDataBackground = model.getModel('selectedDataBackground').option;
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, { width: 5,
+                height: 5,
+                x: (x * thumbnailScale - 2.5),
+                y: (y * thumbnailScale - 2.5)});
+
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+                });

Review Comment:
   wrong indentation here



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,193 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+import { zrUtil } from '../../echarts.all';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailScale = model.getModel('scale').option;
+        const selectedDataBackground = model.getModel('selectedDataBackground').option;
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, { width: 5,
+                height: 5,
+                x: (x * thumbnailScale - 2.5),
+                y: (y * thumbnailScale - 2.5)});

Review Comment:
   having code style issue here



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -229,6 +232,28 @@ export interface GraphSeriesOption
      * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set
      */
     autoCurveness?: boolean | number | number[]
+
+    thumbnail?: {
+        show?: boolean,
+
+        top?: number | string,
+
+        bottom?: number | string,
+
+        left?: number | string,
+
+        right?: number | string,
+
+        scale?: number,
+
+        borderColor?: Color,
+
+        backgroundColor?: Color,
+
+        overlayBackgroundColor?: Color,
+
+        selectedDataBackground?: AreaStyleProps

Review Comment:
   Wrong type here. `AreaStyleProps` is the key list. It should be `ItemStyle`. Also I will prefer the name `selectedBackgroundStyle`



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -229,6 +232,28 @@ export interface GraphSeriesOption
      * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set
      */
     autoCurveness?: boolean | number | number[]
+
+    thumbnail?: {
+        show?: boolean,
+
+        top?: number | string,
+
+        bottom?: number | string,
+
+        left?: number | string,
+
+        right?: number | string,
+
+        scale?: number,
+
+        borderColor?: Color,

Review Comment:
   We usually use an option `itemStyle` to configure all the styles. Only `borderColor` and `backgroundColor` is not enough.



##########
src/model/mixin/areaStyle.ts:
##########
@@ -34,7 +34,7 @@ export const AREA_STYLE_KEY_MAP = [
 ];
 const getAreaStyle = makeStyleMapper(AREA_STYLE_KEY_MAP);
 
-type AreaStyleProps = Pick<PathStyleProps,
+export type AreaStyleProps = Pick<PathStyleProps,

Review Comment:
   No need to export this.



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -229,6 +232,28 @@ export interface GraphSeriesOption
      * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set
      */
     autoCurveness?: boolean | number | number[]
+
+    thumbnail?: {
+        show?: boolean,
+
+        top?: number | string,
+
+        bottom?: number | string,
+
+        left?: number | string,
+
+        right?: number | string,
+
+        scale?: number,
+
+        borderColor?: Color,
+
+        backgroundColor?: Color,
+
+        overlayBackgroundColor?: Color,

Review Comment:
   Seems `overlayBackgroundColor` is not used.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] 100pah commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
100pah commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1295807946

   Some remaining feature defects:
   
   1. The select box will be overflow the thumbnail container.
   <img width="440" alt="image" src="https://user-images.githubusercontent.com/1956569/198828110-d5469a42-1512-449c-b62b-703e0ca2c3cd.png">
   
   2. When the select box is overflow the thumbnail, it disappears. Keeping display is expected.
   
   ---
   
   Some features are required for a releasable product (IMO): 
   1. The select box can be draggable.
   2. Mouse wheel zoom can be performed according to the thumbnail area when mouse hover it, that is, the zoom center is the point on thumbnail are, rather than the graph view.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] Ovilia commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
Ovilia commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1204937459

   Thanks for your contribution. I opened a related discussion at #17432. You are welcomed to do the thumbnail part and I will guide you along with @100pah . I will be responsible to make the roaming experience better.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] echarts-bot[bot] commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
echarts-bot[bot] commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1297283311

   Congratulations! Your PR has been merged. Thanks for your contribution! 👍


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] 100pah merged pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
100pah merged PR #17471:
URL: https://github.com/apache/echarts/pull/17471


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] 100pah commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
100pah commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r1008709082


##########
src/chart/graph/GraphSeries.ts:
##########
@@ -509,6 +535,27 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
             itemStyle: {
                 borderColor: '#212121'
             }
+        },
+
+        thumbnail: {
+            show: false,
+
+            right: 0,
+            bottom: 0,
+
+            height: 200,

Review Comment:
   considering different screen size, I  think the default value can be 'xx%', rather than absolute value.



##########
src/chart/graph/GraphView.ts:
##########
@@ -311,6 +327,14 @@ class GraphView extends ChartView {
     remove(ecModel: GlobalModel, api: ExtensionAPI) {
         this._symbolDraw && this._symbolDraw.remove();
         this._lineDraw && this._lineDraw.remove();
+        this._thumbanil && this.group.remove(this._thumbanil.group);
+    }
+
+    private _renderThumbnail(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        if (this._thumbanil) {
+            this._thumbanil.remove();
+        }
+        (this._thumbanil = new Thumbnail(this.group)).render(seriesModel, api);

Review Comment:
   Consider the code maintainability, 
   the code "add to this.group" and the code "remove from this.ground" should better write in the same file.
   We write `this.group.remove(this._thumbanil.group)` here, 
   we can also `this.grounp.add(this._thumbnail.group)` here.
   And `_thumbnail.remove()` and `this.group.remove(this._thumbanil.group)` seams do the similar thing. So only keep one. We can remove the method `_thumbnail.remove()` and only keep `this.group.remove(this._thumbanil.group)`
   
   
   



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);

Review Comment:
   By echarts convention, the default value do not write here, but in `GraphSeries` `defaultOption`.



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);
+
+        thumbnailGroup.attr(groupNewProp);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            x: coordSys.x,
+            y: coordSys.y,
+            ignore: true,
+            z2: 152
+        });
+
+        group.add(this._selectedRect);
+
+        if (zoom > 1) {
+            this._updateSelectedRect('init');
+        }
+    }
+
+    remove() {
+        this.group.removeAll();
+    }
+
+
+
+    _updateSelectedRect(type: 'zoom' | 'pan' | 'init') {
+        const getNewRect = (min = false) => {
+            const {height, width} = this._layoutParams.box;
+            const origin = [0, 40];

Review Comment:
   Why use the number `40`?



##########
test/graph-thumbnail.html:
##########
@@ -0,0 +1,580 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">

Review Comment:
   The indent is not correct in this file.



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;

Review Comment:
   The class members should be declared as `private` unless it needed to be `public`



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');

Review Comment:
   All of the `model.get('xx')` here , we should better use `model.get('xx', true)`, which will prevent from fetching the value from `echartsOption.xxx`.



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -229,6 +235,26 @@ export interface GraphSeriesOption
      * auto curveness for multiple edge, invalid when `lineStyle.curveness` is set
      */
     autoCurveness?: boolean | number | number[]
+
+    thumbnail?: {
+        show?: boolean,
+
+        top?: number | string,

Review Comment:
   For `top` `bottom` `left` `right` `width` `height`,
   it's better to import and use `BoxLayoutOptionMixin`.



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,

Review Comment:
   Use `zrUtil.clone`



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();

Review Comment:
   Do not obtain `symbolNodes` and `lineNodes` by child index. It's difficult to maintain. If the index changed, we have to change the code here.
   
   If `Thumbnail` needs `symbolNodes` and `lineNodes`, pass them into the `render` method directly.
   
   Actually `Thumbnail` do not need `this._parent`. We can only generate `thumbnail.group` in `Thumbnail`, and add/remove `thumbnail.group` in GraphSeries.ts 



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -509,6 +535,27 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
             itemStyle: {
                 borderColor: '#212121'
             }
+        },
+
+        thumbnail: {
+            show: false,
+
+            right: 0,
+            bottom: 0,
+
+            height: 200,
+
+            width: 200,
+
+            itemStyle: {
+                backgroundColor: 'white',
+                borderColor: 'black'
+            },
+
+            selectedDataBackgroundStyle: {

Review Comment:
   The term "data" has specified meaning in echarts.
   This "selection box" is not "data". 
   And we'd better not to put the term "background" in the attribute? because is config both background and border and other style related to the select box.
   
   I prefer to name it as "selectedAreaStyle", or any better ideas?
   



##########
src/chart/graph/GraphView.ts:
##########
@@ -213,6 +221,7 @@ class GraphView extends ChartView {
         });
 
         this._firstRender = false;
+        this._isForceLayout || this._renderThumbnail(seriesModel, api);

Review Comment:
   `_isForceLayout` is not needed to save as an instance member?
   We should better not to persistent some data on an instance unless really needed.



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);
+
+        thumbnailGroup.attr(groupNewProp);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            x: coordSys.x,
+            y: coordSys.y,
+            ignore: true,
+            z2: 152
+        });
+
+        group.add(this._selectedRect);
+
+        if (zoom > 1) {
+            this._updateSelectedRect('init');
+        }
+    }
+
+    remove() {
+        this.group.removeAll();
+    }
+
+
+
+    _updateSelectedRect(type: 'zoom' | 'pan' | 'init') {

Review Comment:
   When type 'zoom' and 'pan' passed here?



##########
src/chart/graph/GraphSeries.ts:
##########
@@ -509,6 +535,27 @@ class GraphSeriesModel extends SeriesModel<GraphSeriesOption> {
             itemStyle: {
                 borderColor: '#212121'
             }
+        },
+
+        thumbnail: {
+            show: false,
+
+            right: 0,
+            bottom: 0,
+
+            height: 200,
+
+            width: 200,
+
+            itemStyle: {
+                backgroundColor: 'white',
+                borderColor: 'black'
+            },
+
+            selectedDataBackgroundStyle: {
+                fill: 'white',

Review Comment:
   `'fill'` `'stroke'` is used in zrender. The echarts options should not be `'fill'` `'stroke'`.
   It should also be  like `backgroundColor` in this case. 



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,

Review Comment:
   Use clone



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);

Review Comment:
   Use `zrUtil.clone` rather than extends. `shape` may contain Array or Object.



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);
+
+        thumbnailGroup.attr(groupNewProp);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            x: coordSys.x,
+            y: coordSys.y,
+            ignore: true,
+            z2: 152
+        });
+
+        group.add(this._selectedRect);
+
+        if (zoom > 1) {
+            this._updateSelectedRect('init');
+        }
+    }
+
+    remove() {
+        this.group.removeAll();
+    }
+
+
+
+    _updateSelectedRect(type: 'zoom' | 'pan' | 'init') {
+        const getNewRect = (min = false) => {
+            const {height, width} = this._layoutParams.box;
+            const origin = [0, 40];
+            const end = [width, height];
+            const originData = this._graphModel.coordinateSystem.pointToData(origin);
+            const endData = this._graphModel.coordinateSystem.pointToData(end);
+
+            const thumbnailMain = this._coords.dataToPoint(originData as number[]);
+            const thumbnailMax = this._coords.dataToPoint(endData as number[]);
+
+            const newWidth = thumbnailMax[0] - thumbnailMain[0];
+            const newHeight = thumbnailMax[1] - thumbnailMain[1];
+
+            rect.x = thumbnailMain[0];
+            rect.y = thumbnailMain[1];
+
+            rect.shape.width = newWidth;
+            rect.shape.height = newHeight;
+
+            if (min === false) {
+                rect.dirty();
+            }
+        };
+        const rect = this._selectedRect;
+
+        const {x: rMinX, y: rMinY, shape: {width: rWidth, height: rHeight}} = rect;
+        const {x: wMinX, y: wMinY, shape: {width: wWidth, height: wHeight}} = this._wrapper;
+
+        const [rMaxX, rMaxY] = [rMinX + rWidth, rMinY + rHeight];
+        const [wMaxX, wMaxY] = [wMinX + wWidth, wMinY + wHeight];
+
+        if (type === 'init') {
+            rect.show();
+            getNewRect();
+            return;
+        }
+        else if (type === 'zoom' && rWidth < wWidth / 10) {
+            getNewRect(true);
+            return;
+        }
+        if (rMinX > wMinX + 5 && rMinY > wMinY + 5 && rMaxX < wMaxX && rMaxY < wMaxY) {

Review Comment:
   Why use the number `5` ?



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);
+
+        thumbnailGroup.attr(groupNewProp);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            x: coordSys.x,
+            y: coordSys.y,
+            ignore: true,
+            z2: 152
+        });
+
+        group.add(this._selectedRect);
+
+        if (zoom > 1) {

Review Comment:
   Why only when `zoom > 1` do `this._updateSelectedRect`?
   When `zoom < 1` and `=== 1`, what will happen ?



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');

Review Comment:
   As mentioned above, should convert attributes like 'backgroundColor' to `fill`, so use code like:
   ```js
   const styleModel = module.getModel('xxxStyle');
   const style = styleModel.getAreaStyle();
   ```



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;

Review Comment:
   typo: offsetX



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);
+
+        thumbnailGroup.attr(groupNewProp);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            x: coordSys.x,
+            y: coordSys.y,
+            ignore: true,
+            z2: 152
+        });
+
+        group.add(this._selectedRect);
+
+        if (zoom > 1) {
+            this._updateSelectedRect('init');
+        }
+    }
+
+    remove() {
+        this.group.removeAll();
+    }
+
+
+
+    _updateSelectedRect(type: 'zoom' | 'pan' | 'init') {
+        const getNewRect = (min = false) => {
+            const {height, width} = this._layoutParams.box;
+            const origin = [0, 40];
+            const end = [width, height];
+            const originData = this._graphModel.coordinateSystem.pointToData(origin);
+            const endData = this._graphModel.coordinateSystem.pointToData(end);
+
+            const thumbnailMain = this._coords.dataToPoint(originData as number[]);
+            const thumbnailMax = this._coords.dataToPoint(endData as number[]);
+
+            const newWidth = thumbnailMax[0] - thumbnailMain[0];
+            const newHeight = thumbnailMax[1] - thumbnailMain[1];
+
+            rect.x = thumbnailMain[0];
+            rect.y = thumbnailMain[1];
+
+            rect.shape.width = newWidth;
+            rect.shape.height = newHeight;
+
+            if (min === false) {
+                rect.dirty();
+            }
+        };
+        const rect = this._selectedRect;
+
+        const {x: rMinX, y: rMinY, shape: {width: rWidth, height: rHeight}} = rect;
+        const {x: wMinX, y: wMinY, shape: {width: wWidth, height: wHeight}} = this._wrapper;
+
+        const [rMaxX, rMaxY] = [rMinX + rWidth, rMinY + rHeight];
+        const [wMaxX, wMaxY] = [wMinX + wWidth, wMinY + wHeight];
+
+        if (type === 'init') {
+            rect.show();
+            getNewRect();
+            return;
+        }
+        else if (type === 'zoom' && rWidth < wWidth / 10) {
+            getNewRect(true);
+            return;
+        }
+        if (rMinX > wMinX + 5 && rMinY > wMinY + 5 && rMaxX < wMaxX && rMaxY < wMaxY) {
+            this._selectedRect.show();
+        }
+        else {
+            this._selectedRect.hide();

Review Comment:
   as mentioned in the comment, I think the logic `hide` is not reasonable.
   should be always show and clipped by the wrapper



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,
+                z2: 151
+            });
+            symbolGroup.add(subThumbnail);
+        }
+
+        for (const node of lineNodes) {
+            const line = (node as graphic.Group).children()[0];
+            const lineThumbnail = new ECLinePath({
+                style: (line as ECLinePath).style,
+                shape: (line as ECLinePath).shape,
+                z2: 151
+            });
+            lineGroup.add(lineThumbnail);
+        }
+
+        thumbnailGroup.add(symbolGroup);
+        thumbnailGroup.add(lineGroup);
+
+        const thumbnailWrapper = new graphic.Rect({
+            style: {
+                stroke: borderColor,
+                fill: backgroundColor
+            },
+            shape: {
+                height: thumbnailHeight,
+                width: thumbnailWidth
+            },
+            z2: 150
+        });
+
+        this._wrapper = thumbnailWrapper;
+
+        group.add(thumbnailGroup);
+        group.add(thumbnailWrapper);
+
+        layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box);
+
+        const coordSys = new View();
+        const boundingRect = this._parent.children()[0].getBoundingRect();
+        coordSys.setBoundingRect(boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height);
+
+        this._coords = coordSys;
+
+        const viewRect = getViewRect(layoutParams, thumbnailWrapper.shape, boundingRect.width / boundingRect.height);
+
+        const scaleX = viewRect.width / boundingRect.width;
+        const scaleY = viewRect.height / boundingRect.height;
+        const offectX = (thumbnailWidth - boundingRect.width * scaleX) / 2;
+        const offectY = (thumbnailHeight - boundingRect.height * scaleY) / 2;
+
+
+        coordSys.setViewRect(
+            thumbnailWrapper.x + offectX,
+            thumbnailWrapper.y + offectY,
+            viewRect.width,
+            viewRect.height
+        );
+
+        const groupNewProp = {
+            x: coordSys.x,
+            y: coordSys.y,
+            scaleX,
+            scaleY
+        };
+
+        const selectStyle = zrUtil.extend({lineWidth: 1, stroke: 'black'}, selectedDataBackground);
+
+        thumbnailGroup.attr(groupNewProp);
+
+        this._selectedRect = new graphic.Rect({
+            style: selectStyle,
+            x: coordSys.x,
+            y: coordSys.y,
+            ignore: true,
+            z2: 152
+        });
+
+        group.add(this._selectedRect);
+
+        if (zoom > 1) {
+            this._updateSelectedRect('init');
+        }
+    }
+
+    remove() {
+        this.group.removeAll();
+    }
+
+
+
+    _updateSelectedRect(type: 'zoom' | 'pan' | 'init') {
+        const getNewRect = (min = false) => {
+            const {height, width} = this._layoutParams.box;
+            const origin = [0, 40];
+            const end = [width, height];
+            const originData = this._graphModel.coordinateSystem.pointToData(origin);
+            const endData = this._graphModel.coordinateSystem.pointToData(end);
+
+            const thumbnailMain = this._coords.dataToPoint(originData as number[]);
+            const thumbnailMax = this._coords.dataToPoint(endData as number[]);
+
+            const newWidth = thumbnailMax[0] - thumbnailMain[0];
+            const newHeight = thumbnailMax[1] - thumbnailMain[1];
+
+            rect.x = thumbnailMain[0];
+            rect.y = thumbnailMain[1];
+
+            rect.shape.width = newWidth;
+            rect.shape.height = newHeight;
+
+            if (min === false) {
+                rect.dirty();
+            }
+        };
+        const rect = this._selectedRect;
+
+        const {x: rMinX, y: rMinY, shape: {width: rWidth, height: rHeight}} = rect;
+        const {x: wMinX, y: wMinY, shape: {width: wWidth, height: wHeight}} = this._wrapper;
+
+        const [rMaxX, rMaxY] = [rMinX + rWidth, rMinY + rHeight];
+        const [wMaxX, wMaxY] = [wMinX + wWidth, wMinY + wHeight];
+
+        if (type === 'init') {
+            rect.show();
+            getNewRect();
+            return;
+        }
+        else if (type === 'zoom' && rWidth < wWidth / 10) {

Review Comment:
   When this branch entered?



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,248 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import * as zrUtil from 'zrender/src/core/util';
+import View from '../../coord/View';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+function getViewRect(layoutParams: LayoutParams, wrapperShpae: {width: number, height: number}, aspect: number) {
+    const option = zrUtil.extend(layoutParams, {
+        aspect: aspect
+    });
+    return layout.getLayoutRect(option, {
+        width: wrapperShpae.width,
+        height: wrapperShpae.height
+    });
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+
+    _parent: graphic.Group;
+
+    _selectedRect: graphic.Rect;
+
+    _layoutParams: LayoutParams;
+
+    _graphModel: GraphSeriesModel;
+
+    _wrapper: graphic.Rect;
+
+    _coords: View;
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+        group.removeAll();
+        if (!model.get('show')) {
+            return;
+        }
+
+        this._graphModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const lineGroup = new graphic.Group();
+        const symbolGroup = new graphic.Group();
+
+        const zoom = seriesModel.get('zoom');
+
+        const itemStyle = model.getModel('itemStyle');
+        const thumbnailHeight = model.get('height');
+        const thumbnailWidth = model.get('width');
+        const selectedDataBackground = model.get('selectedDataBackgroundStyle');
+        const backgroundColor = itemStyle.get('backgroundColor');
+        const borderColor = itemStyle.get('borderColor');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+        const layoutParams = this._layoutParams;
+
+        const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+            const sub = (node as graphic.Group).children()[0];
+            const x = (node as SymbolClz).x;
+            const y = (node as SymbolClz).y;
+            const subShape = zrUtil.extend({}, (sub as graphic.Path).shape);
+            const shape = zrUtil.extend(subShape, {
+                width: sub.scaleX,
+                height: sub.scaleY,
+                x: x - sub.scaleX / 2,
+                y: y - sub.scaleY / 2
+            });
+            const subThumbnail = new (sub as any).constructor({
+                shape,
+                style: (sub as graphic.Path).style,

Review Comment:
   Use `zrUtil.clone`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] Lruler closed pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
Lruler closed pull request #17471: [feat]: add thumbnails components
URL: https://github.com/apache/echarts/pull/17471


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] pissang commented on a diff in pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
pissang commented on code in PR #17471:
URL: https://github.com/apache/echarts/pull/17471#discussion_r996395233


##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;

Review Comment:
   We prefer using `(thumbnailWidth as number)` to do type assertion



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,

Review Comment:
   We use `zrUtil.extend` instead of spread syntax becase it will bring extra code after compiled.



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;

Review Comment:
   Seems `_widthProportion` and `_heightProportion` are not used?



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;

Review Comment:
   The name should be `graphModel`?



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,
+                        width: 5,
+                        height: 5,
+                        x: (x * 0.25 - 2.5),
+                        y: (y * 0.25 - 2.5)
+                      },
+                  style: (sub as graphic.Path).style,
+                  z: 3,
+                  z2: 151
+              });
+
+              thumbnailGroup.add(subThumbnail);
+        }
+
+      for (const node of lineNodes) {
+          const line = (node as graphic.Group).children()[0];
+          const lineThumbnail = new ECLinePath({
+              style: (line as ECLinePath).style,
+              shape: (line as ECLinePath).shape,
+              scaleX: 0.25,
+              scaleY: 0.25,
+              z: 3,

Review Comment:
   `z` will be force set in https://github.com/pe-3/echarts-funnel-newfeat/blob/a236499bcfea9b990462b7138e7d3357d3590c8b/src/core/echarts.ts#L2373



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,
+                        width: 5,
+                        height: 5,
+                        x: (x * 0.25 - 2.5),
+                        y: (y * 0.25 - 2.5)
+                      },
+                  style: (sub as graphic.Path).style,
+                  z: 3,
+                  z2: 151
+              });
+
+              thumbnailGroup.add(subThumbnail);
+        }
+
+      for (const node of lineNodes) {
+          const line = (node as graphic.Group).children()[0];
+          const lineThumbnail = new ECLinePath({
+              style: (line as ECLinePath).style,
+              shape: (line as ECLinePath).shape,
+              scaleX: 0.25,

Review Comment:
   What's `scaleX` and `scaleY` for?



##########
src/chart/graph/Thumbnail.ts:
##########
@@ -0,0 +1,204 @@
+import * as graphic from '../../util/graphic';
+import ExtensionAPI from '../../core/ExtensionAPI';
+import * as layout from '../../util/layout';
+import { BoxLayoutOptionMixin } from '../../util/types';
+import SymbolClz from '../helper/Symbol';
+import ECLinePath from '../helper/LinePath';
+import GraphSeriesModel from './GraphSeries';
+import { RoamEventParams } from '../../component/helper/RoamController';
+import { clone } from 'zrender/src/core/util';
+
+interface LayoutParams {
+    pos: BoxLayoutOptionMixin
+    box: {
+        width: number,
+        height: number
+    }
+}
+
+class Thumbnail {
+
+    group = new graphic.Group();
+    _parent: graphic.Group;
+
+    _widthProportion: number;
+    _heightProportion: number;
+
+    _selectedRect: graphic.Rect;
+    _wrapper: graphic.Rect;
+    _layoutParams: LayoutParams;
+
+    _treeModel: GraphSeriesModel;
+
+    constructor(containerGroup: graphic.Group) {
+        containerGroup.add(this.group);
+        this._parent = containerGroup;
+    }
+
+    render(seriesModel: GraphSeriesModel, api: ExtensionAPI) {
+        const model = seriesModel.getModel('thumbnail');
+        const group = this.group;
+
+        group.removeAll();
+        if (!model.get('show')) {
+          return;
+        }
+
+        this._treeModel = seriesModel;
+
+        const childrenNodes = (this._parent.children()[0] as graphic.Group).children();
+        const symbolNodes = (childrenNodes[0] as graphic.Group).children();
+        const lineNodes = (childrenNodes[1] as graphic.Group).children();
+
+        const backgroundColor = model.getModel('backgroundColor');
+        const borderColor = model.getModel('borderColor');
+        const thumbnailWidth = model.get('width');
+        const thumbnailHeight = model.get('height');
+        const selectedDataBackground = model.getModel('selectedDataBackground');
+
+        this._layoutParams = {
+            pos: {
+                left: model.get('left'),
+                right: model.get('right'),
+                top: model.get('top'),
+                bottom: model.get('bottom')
+            },
+            box: {
+                width: api.getWidth(),
+                height: api.getHeight()
+            }
+        };
+
+          const layoutParams = this._layoutParams;
+
+          this._widthProportion = <number>thumbnailWidth / layoutParams.box.width;
+          this._heightProportion = <number>thumbnailHeight / layoutParams.box.height;
+
+          const thumbnailGroup = new graphic.Group();
+
+        for (const node of symbolNodes) {
+              const sub = (node as graphic.Group).children()[0];
+              const x = (node as SymbolClz).x;
+              const y = (node as SymbolClz).y;
+
+              const subThumbnail = new (sub as any).constructor({
+                  shape: {
+                        ...(sub as graphic.Path).shape,
+                        width: 5,
+                        height: 5,
+                        x: (x * 0.25 - 2.5),
+                        y: (y * 0.25 - 2.5)
+                      },
+                  style: (sub as graphic.Path).style,
+                  z: 3,
+                  z2: 151
+              });
+
+              thumbnailGroup.add(subThumbnail);
+        }
+
+      for (const node of lineNodes) {
+          const line = (node as graphic.Group).children()[0];
+          const lineThumbnail = new ECLinePath({
+              style: (line as ECLinePath).style,
+              shape: (line as ECLinePath).shape,
+              scaleX: 0.25,
+              scaleY: 0.25,
+              z: 3,
+              z2: 151
+          });
+          thumbnailGroup.add(lineThumbnail);
+      }
+
+      const thumbnailWrapper = new graphic.Rect({
+          style: {
+              stroke: borderColor.option,
+              fill: backgroundColor.option,
+              lineWidth: 2
+          },
+          shape: {
+              height: thumbnailHeight === 0 ? layoutParams.box.height / 4 : thumbnailHeight,
+              width: thumbnailWidth === 0 ? layoutParams.box.width / 4 : thumbnailWidth
+          },
+          z: 2,
+          z2: 130
+      });
+
+      this._wrapper = thumbnailWrapper;
+
+      const areaStyle = selectedDataBackground.get('areaStyle');

Review Comment:
   It's not necessary to separate to `areaStyle` and `lineStyle`. Just use one `selectedBackgroundStyle` and use `getItemStyle` to map to zrender style



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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


[GitHub] [echarts] echarts-bot[bot] commented on pull request #17471: [feat]: add thumbnails components

Posted by GitBox <gi...@apache.org>.
echarts-bot[bot] commented on PR #17471:
URL: https://github.com/apache/echarts/pull/17471#issuecomment-1204881481

   Thanks for your contribution!
   The community will review it ASAP. In the meanwhile, please checkout [the coding standard](https://echarts.apache.org/en/coding-standard.html) and Wiki about [How to make a pull request](https://github.com/apache/echarts/wiki/How-to-make-a-pull-request).
   
   ⚠️ MISSING DOCUMENT INFO: Please make sure one of the document options are checked in this PR's description. Search "Document Info" in the description of this PR. This should be done either by the author or the reviewers of the PR.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


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