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-unsubscr...@echarts.apache.org

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


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

Reply via email to