From f7e61f20c1f91431d4ef4f70b5ecd392dc764a13 Mon Sep 17 00:00:00 2001 From: 100pah Date: Thu, 8 Dec 2022 15:02:08 +0800 Subject: [PATCH] feat(thumbnail): upgrade thumbnail (1) Enable pan and zoom on thumbnail (2) Rename selectedAreaStyle to windowStyle (3) Clean up the code (4) Support border-radius and clip. --- src/chart/graph/GraphSeries.ts | 34 +- src/chart/graph/GraphView.ts | 161 ++++++-- src/chart/graph/Thumbnail.ts | 429 ++++++++++----------- src/chart/tree/TreeView.ts | 6 +- src/component/helper/RoamController.ts | 17 +- src/component/helper/roamHelper.ts | 12 +- src/util/layout.ts | 37 +- test/graph-thumbnail.html | 505 +++++-------------------- 8 files changed, 492 insertions(+), 709 deletions(-) diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts index 5d69a30f3e..3d96ac34e7 100644 --- a/src/chart/graph/GraphSeries.ts +++ b/src/chart/graph/GraphSeries.ts @@ -43,8 +43,7 @@ import { GraphEdgeItemObject, OptionDataValueNumeric, CallbackDataParams, - DefaultEmphasisFocus, - ZRColor + DefaultEmphasisFocus } from '../../util/types'; import SeriesModel from '../../model/Series'; import Graph from '../../data/Graph'; @@ -55,6 +54,7 @@ import { LineDataVisual } from '../../visual/commonVisualTypes'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip'; import {initCurvenessList, createEdgeMapForCurveness} from '../helper/multipleGraphEdgeHelper'; +import Thumbnail, { ThumbnailOption } from './Thumbnail'; type GraphDataValue = OptionDataValue | OptionDataValue[]; @@ -230,13 +230,7 @@ export interface GraphSeriesOption */ autoCurveness?: boolean | number | number[] - thumbnail?: BoxLayoutOptionMixin & { - show?: boolean, - - itemStyle?: ItemStyleOption - - selectedAreaStyle?: ItemStyleOption - } + thumbnail?: ThumbnailOption } class GraphSeriesModel extends SeriesModel { @@ -519,27 +513,7 @@ class GraphSeriesModel extends SeriesModel { } }, - thumbnail: { - show: false, - - right: 0, - bottom: 0, - - height: '25%', - width: '25%', - - itemStyle: { - color: 'white', - borderColor: 'black' - }, - - selectedAreaStyle: { - color: 'white', - borderColor: 'black', - borderWidth: 1, - opacity: 0.5 - } - } + thumbnail: Thumbnail.defaultOption }; } diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index 329cfe5cf5..f5c4b7ba68 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -19,8 +19,12 @@ import SymbolDraw, { ListForSymbolDraw } from '../helper/SymbolDraw'; import LineDraw from '../helper/LineDraw'; -import RoamController, { RoamControllerHost } from '../../component/helper/RoamController'; -import * as roamHelper from '../../component/helper/roamHelper'; +import RoamController from '../../component/helper/RoamController'; +import { + updateViewOnZoom, + updateViewOnPan, + RoamControllerHost +} from '../../component/helper/roamHelper'; import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import * as graphic from '../../util/graphic'; import adjustEdge from './adjustEdge'; @@ -39,6 +43,8 @@ import Thumbnail from './Thumbnail'; import { simpleLayoutEdge } from './simpleLayoutHelper'; import { circularLayout, rotateNodeLabel } from './circularLayoutHelper'; +import { clone, extend } from 'zrender/src/core/util'; +import ECLinePath from '../helper/LinePath'; function isViewCoordSys(coordSys: CoordinateSystem): coordSys is View { return coordSys.type === 'view'; @@ -63,7 +69,7 @@ class GraphView extends ChartView { private _layouting: boolean; - private _thumbnail: Thumbnail; + private _thumbnail: Thumbnail = new Thumbnail(); private _mainGroup: graphic.Group; @@ -218,9 +224,10 @@ class GraphView extends ChartView { dispose() { this._controller && this._controller.dispose(); this._controllerHost = null; + this._thumbnail.dispose(); } - _startForceLayoutIteration( + private _startForceLayoutIteration( forceLayout: GraphSeriesModel['forceLayout'], api: ExtensionAPI, layoutAnimation?: boolean @@ -241,7 +248,7 @@ class GraphView extends ChartView { })(); } - _updateController( + private _updateController( seriesModel: GraphSeriesModel, ecModel: GlobalModel, api: ExtensionAPI @@ -250,10 +257,11 @@ class GraphView extends ChartView { const controllerHost = this._controllerHost; const group = this.group; - controller.setPointerChecker(function (e, x, y) { + controller.setPointerChecker((e, x, y) => { const rect = group.getBoundingRect(); rect.applyTransform(group.transform); return rect.contain(x, y) + && !this._thumbnail.contain(x, y) && !onIrrelevantElement(e, api, seriesModel); }); @@ -269,34 +277,53 @@ class GraphView extends ChartView { .off('pan') .off('zoom') .on('pan', (e) => { - roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy); - api.dispatchAction({ - seriesId: seriesModel.id, - type: 'graphRoam', - dx: e.dx, - dy: e.dy - }); - this._thumbnail._updateSelectedRect('pan'); + this._updateViewOnPan(seriesModel, api, e.dx, e.dy); }) .on('zoom', (e) => { - roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); - api.dispatchAction({ - seriesId: seriesModel.id, - type: 'graphRoam', - zoom: e.scale, - originX: e.originX, - originY: e.originY - }); - this._updateNodeAndLinkScale(); - adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); - this._lineDraw.updateLayout(); - // Only update label layout on zoom - api.updateLabelLayout(); - this._thumbnail._updateSelectedRect('zoom'); + this._updateViewOnZoom(seriesModel, api, e.scale, e.originX, e.originY); }); } - _updateNodeAndLinkScale() { + private _updateViewOnPan( + seriesModel: GraphSeriesModel, + api: ExtensionAPI, + dx: number, + dy: number + ): void { + updateViewOnPan(this._controllerHost, dx, dy); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'graphRoam', + dx: dx, + dy: dy + }); + this._thumbnail.updateWindow(); + } + + private _updateViewOnZoom( + seriesModel: GraphSeriesModel, + api: ExtensionAPI, + scale: number, + originX: number, + originY: number + ) { + updateViewOnZoom(this._controllerHost, scale, originX, originY); + api.dispatchAction({ + seriesId: seriesModel.id, + type: 'graphRoam', + zoom: scale, + originX: originX, + originY: originY + }); + this._updateNodeAndLinkScale(); + adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel)); + this._lineDraw.updateLayout(); + // Only update label layout on zoom + api.updateLabelLayout(); + this._thumbnail.updateWindow(); + } + + private _updateNodeAndLinkScale() { const seriesModel = this._model; const data = seriesModel.getData(); @@ -317,19 +344,85 @@ class GraphView extends ChartView { remove(ecModel: GlobalModel, api: ExtensionAPI) { this._symbolDraw && this._symbolDraw.remove(); this._lineDraw && this._lineDraw.remove(); - this._thumbnail && this.group.remove(this._thumbnail.group); + this._controller && this._controller.disable(); + this._thumbnail.remove(); } + // TODO: register thumbnail (consider code size). private _renderThumbnail( seriesModel: GraphSeriesModel, api: ExtensionAPI, symbolDraw: SymbolDraw, lineDraw: LineDraw ) { - if (this._thumbnail) { - this.group.remove(this._thumbnail.group); - } - (this._thumbnail = new Thumbnail(this.group)).render(seriesModel, api, symbolDraw, lineDraw, this._mainGroup); + const thumbnail = this._thumbnail; + this.group.add(thumbnail.group); + + const renderThumbnailContent = (viewGroup: graphic.Group) => { + const symbolNodes = symbolDraw.group.children(); + const lineNodes = lineDraw.group.children(); + + const lineGroup = new graphic.Group(); + const symbolGroup = new graphic.Group(); + viewGroup.add(symbolGroup); + viewGroup.add(lineGroup); + + for (let i = 0; i < symbolNodes.length; i++) { + const node = symbolNodes[i]; + const sub = (node as graphic.Group).children()[0]; + const x = (node as Symbol).x; + const y = (node as Symbol).y; + const subShape = clone((sub as graphic.Path).shape); + const shape = extend(subShape, { + width: sub.scaleX, + height: sub.scaleY, + x: x - sub.scaleX / 2, + y: y - sub.scaleY / 2 + }); + const style = clone((sub as graphic.Path).style); + const subThumbnail = new (sub as any).constructor({ + shape, + style, + z2: 151 + }); + symbolGroup.add(subThumbnail); + } + + for (let i = 0; i < lineNodes.length; i++) { + const node = lineNodes[i]; + const line = (node as graphic.Group).children()[0]; + const style = clone((line as ECLinePath).style); + const shape = clone((line as ECLinePath).shape); + const lineThumbnail = new ECLinePath({ + style, + shape, + z2: 151 + }); + lineGroup.add(lineThumbnail); + } + }; + + thumbnail.render({ + seriesModel, + api, + roamType: seriesModel.get('roam'), + z2Setting: { + background: 150, + window: 160 + }, + seriesBoundingRect: this._mainGroup.getBoundingRect(), + renderThumbnailContent + }); + + thumbnail + .off('pan') + .off('zoom') + .on('pan', (event) => { + this._updateViewOnPan(seriesModel, api, event.dx, event.dy); + }) + .on('zoom', (event) => { + this._updateViewOnZoom(seriesModel, api, event.scale, event.originX, event.originY); + }); } } diff --git a/src/chart/graph/Thumbnail.ts b/src/chart/graph/Thumbnail.ts index bb2f2dab8e..f478584377 100644 --- a/src/chart/graph/Thumbnail.ts +++ b/src/chart/graph/Thumbnail.ts @@ -1,265 +1,272 @@ 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'; -import SymbolDraw from '../helper/SymbolDraw'; -import LineDraw from '../helper/LineDraw'; - -interface LayoutParams { - pos: BoxLayoutOptionMixin - box: { - width: number, - height: number - } +import BoundingRect from 'zrender/src/core/BoundingRect'; +import * as matrix from 'zrender/src/core/matrix'; +import * as vector from 'zrender/src/core/vector'; +import SeriesModel from '../../model/Series'; +import { BoxLayoutOptionMixin, ItemStyleOption } from '../../util/types'; +import RoamController, { RoamEventDefinition, RoamType } from '../../component/helper/RoamController'; +import Eventful from 'zrender/src/core/Eventful'; + + +// TODO: +// Thumbnail should not be bound to a single series when used on +// coordinate system like cartesian and geo/map? +// Should we make thumbnail as a component like markers/axisPointer/brush did? + + +interface BorderRadiusOption { + borderRadius?: number | 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 - }); +// TODO: apply to other series +export interface ThumbnailOption extends BoxLayoutOptionMixin { + show?: boolean, + itemStyle?: ItemStyleOption & BorderRadiusOption + windowStyle?: ItemStyleOption & BorderRadiusOption } -class Thumbnail { +interface WindowRect extends graphic.Rect { + __r?: BorderRadiusOption['borderRadius']; +} - group = new graphic.Group(); +export interface ThumbnailZ2Setting { + background: number; + window: number; +} - private _selectedRect: graphic.Rect; +class Thumbnail extends Eventful> { - private _layoutParams: LayoutParams; + group = new graphic.Group(); - private _graphModel: GraphSeriesModel; + private _api: ExtensionAPI; + private _seriesModel: GraphSeriesModel; - private _wrapper: graphic.Rect; + private _windowRect: WindowRect; + private _contentBoundingRect: BoundingRect; + private _thumbnailCoordSys: View; - private _coords: View; + private _mtSeriesToThumbnail: matrix.MatrixArray; + private _mtThumbnailToSerise: matrix.MatrixArray; - constructor(containerGroup: graphic.Group) { - containerGroup.add(this.group); - } + private _thumbnailController: RoamController; + private _isEnabled: boolean; + + render(opt: { + seriesModel: GraphSeriesModel; + api: ExtensionAPI; + roamType: RoamType; + z2Setting: ThumbnailZ2Setting; + seriesBoundingRect: BoundingRect, + renderThumbnailContent: (viewGroup: graphic.Group) => void + }) { + const seriesModel = this._seriesModel = opt.seriesModel; + const api = this._api = opt.api; - render( - seriesModel: GraphSeriesModel, - api: ExtensionAPI, - symbolDraw: SymbolDraw, - lineDraw: LineDraw, - graph: graphic.Group - ) { - const model = seriesModel.getModel('thumbnail'); + const thumbnailModel = seriesModel.getModel('thumbnail'); const group = this.group; - group.removeAll(); - if (!model.get('show')) { + + this._isEnabled = thumbnailModel.get('show', true) && isSeriesSupported(seriesModel); + if (!this._isEnabled) { + this._clear(); return; } - this._graphModel = seriesModel; - const symbolNodes = symbolDraw.group.children(); - const lineNodes = lineDraw.group.children(); - - const lineGroup = new graphic.Group(); - const symbolGroup = new graphic.Group(); - - const zoom = seriesModel.get('zoom', true); + group.removeAll(); - const itemStyleModel = model.getModel('itemStyle'); + const z2Setting = opt.z2Setting; + const cursor = opt.roamType ? 'pointer' : 'default'; + const itemStyleModel = thumbnailModel.getModel('itemStyle'); const itemStyle = itemStyleModel.getItemStyle(); - const selectStyleModel = model.getModel('selectedAreaStyle'); - const selectStyle = selectStyleModel.getItemStyle(); - const thumbnailHeight = this._handleThumbnailShape(model.get('height', true), api, 'height'); - const thumbnailWidth = this._handleThumbnailShape(model.get('width', true), api, 'width'); - - this._layoutParams = { - pos: { - left: model.get('left'), - right: model.get('right'), - top: model.get('top'), - bottom: model.get('bottom') + itemStyle.fill = seriesModel.ecModel.get('backgroundColor') || '#fff'; + + // Try to use border-box in thumbnail, see https://github.com/apache/echarts/issues/18022 + const boxBorderWidth = itemStyle.lineWidth || 0; + const boxContainBorder = layout.getLayoutRect( + { + left: thumbnailModel.get('left', true), + top: thumbnailModel.get('top', true), + right: thumbnailModel.get('right', true), + bottom: thumbnailModel.get('bottom', true), + width: thumbnailModel.get('width', true), + height: thumbnailModel.get('height', true) }, - 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.clone((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 style = zrUtil.clone((sub as graphic.Path).style); - const subThumbnail = new (sub as any).constructor({ - shape, - style, - z2: 151 - }); - symbolGroup.add(subThumbnail); - } - - for (const node of lineNodes) { - const line = (node as graphic.Group).children()[0]; - const style = zrUtil.clone((line as ECLinePath).style); - const shape = zrUtil.clone((line as ECLinePath).shape); - const lineThumbnail = new ECLinePath({ - style, - shape, - z2: 151 - }); - lineGroup.add(lineThumbnail); - } - - thumbnailGroup.add(symbolGroup); - thumbnailGroup.add(lineGroup); - - const thumbnailWrapper = new graphic.Rect({ + ); + const borderBoundingRect = + layout.applyPedding(boxContainBorder.clone(), boxBorderWidth / 2); + const contentBoundingRect = this._contentBoundingRect = + layout.applyPedding(boxContainBorder.clone(), boxBorderWidth); + + const clipGroup = new graphic.Group(); + group.add(clipGroup); + clipGroup.setClipPath(new graphic.Rect({ + shape: contentBoundingRect.plain() + })); + + const seriesViewGroup = new graphic.Group(); + clipGroup.add(seriesViewGroup); + opt.renderThumbnailContent(seriesViewGroup); + + // Draw border and background and shadow of thumbnail box. + group.add(new graphic.Rect({ style: itemStyle, - shape: { - height: thumbnailHeight, - width: thumbnailWidth + shape: zrUtil.extend(borderBoundingRect.plain(), { + r: itemStyleModel.get('borderRadius', true) + }), + cursor, + z2: z2Setting.background + })); + + const coordSys = this._thumbnailCoordSys = new View(); + const seriesBoundingRect = opt.seriesBoundingRect; + coordSys.setBoundingRect( + seriesBoundingRect.x, seriesBoundingRect.y, seriesBoundingRect.width, seriesBoundingRect.height + ); + + // Find an approperiate rect in contentBoundingRect for the entire graph. + const graphViewRect = layout.getLayoutRect( + { + left: 'center', + top: 'center', + aspect: seriesBoundingRect.width / seriesBoundingRect.height }, - z2: 150 + contentBoundingRect + ); + coordSys.setViewRect(graphViewRect.x, graphViewRect.y, graphViewRect.width, graphViewRect.height); + seriesViewGroup.attr(coordSys.getTransformInfo().raw); + + const windowStyleModel = thumbnailModel.getModel('windowStyle'); + const windowRect: WindowRect = this._windowRect = new graphic.Rect({ + style: windowStyleModel.getItemStyle(), + cursor, + z2: z2Setting.window }); + windowRect.__r = windowStyleModel.get('borderRadius', true); + clipGroup.add(windowRect); - this._wrapper = thumbnailWrapper; - - group.add(thumbnailGroup); - group.add(thumbnailWrapper); - - layout.positionElement(thumbnailWrapper, layoutParams.pos, layoutParams.box); - - const coordSys = new View(); - const boundingRect = graph.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 offsetX = (thumbnailWidth - boundingRect.width * scaleX) / 2; - const offsetY = (thumbnailHeight - boundingRect.height * scaleY) / 2; + this._resetRoamController(opt.roamType); + this.updateWindow(); + } - coordSys.setViewRect( - thumbnailWrapper.x + offsetX, - thumbnailWrapper.y + offsetY, - viewRect.width, - viewRect.height - ); + /** + * Update window by series view roam status. + */ + updateWindow(): void { + if (!this._isEnabled) { + return; + } - const groupNewProp = { - x: coordSys.x, - y: coordSys.y, - scaleX, - scaleY - }; - - thumbnailGroup.attr(groupNewProp); - - this._selectedRect = new graphic.Rect({ - style: selectStyle, - x: coordSys.x, - y: coordSys.y, - // ignore: true, - z2: 152 - }); + this._updateTransform(); - group.add(this._selectedRect); + const rect = new BoundingRect(0, 0, this._api.getWidth(), this._api.getHeight()); + rect.applyTransform(this._mtSeriesToThumbnail); + const windowRect = this._windowRect; + windowRect.setShape(zrUtil.defaults({r: windowRect.__r}, rect)); + } - if (zoom > 1) { - this._updateSelectedRect('init'); - } + /** + * Create transform that convert pixel vector from + * series coordinate system to thumbnail coordinate system. + * + * TODO: consider other type of series. + */ + private _updateTransform(): void { + const seriesCoordSys = this._seriesModel.coordinateSystem as View; + this._mtSeriesToThumbnail = matrix.mul([], this._thumbnailCoordSys.transform, seriesCoordSys.invTransform); + this._mtThumbnailToSerise = matrix.invert([], this._mtSeriesToThumbnail); } - _updateSelectedRect(type: 'zoom' | 'pan' | 'init') { - const getNewRect = (min = false) => { - const {height, width} = this._layoutParams.box; - const origin = [0, 0]; - const end = [width, height]; - const originData = this._graphModel.coordinateSystem.pointToData(origin); - const endData = this._graphModel.coordinateSystem.pointToData(end); + private _resetRoamController(roamType: RoamType): void { + let thumbnailController = this._thumbnailController; + if (!thumbnailController) { + thumbnailController = this._thumbnailController = new RoamController(this._api.getZr()); + thumbnailController.setPointerChecker((e, x, y) => this.contain(x, y)); + } - const thumbnailMain = this._coords.dataToPoint(originData as number[]); - const thumbnailMax = this._coords.dataToPoint(endData as number[]); + thumbnailController.enable(roamType); + thumbnailController + .off('pan') + .off('zoom') + .on('pan', (event) => { + const transform = this._mtThumbnailToSerise; + const oldOffset = vector.applyTransform([], [event.oldX, event.oldY], transform); + // reverse old and new because we pan window rather graph in thumbnail. + const newOffset = vector.applyTransform([], [event.oldX - event.dx, event.oldY - event.dy], transform); + this.trigger('pan', { + dx: newOffset[0] - oldOffset[0], + dy: newOffset[1] - oldOffset[1], + oldX: oldOffset[0], + oldY: oldOffset[1], + newX: newOffset[0], + newY: newOffset[1], + isAvailableBehavior: event.isAvailableBehavior + }); + }) + .on('zoom', (event) => { + const offset = vector.applyTransform([], [event.originX, event.originY], this._mtThumbnailToSerise); + this.trigger('zoom', { + scale: 1 / event.scale, + originX: offset[0], + originY: offset[1], + isAvailableBehavior: event.isAvailableBehavior + }); + }); + } - const newWidth = thumbnailMax[0] - thumbnailMain[0]; - const newHeight = thumbnailMax[1] - thumbnailMain[1]; + contain(x: number, y: number): boolean { + return this._contentBoundingRect && this._contentBoundingRect.contain(x, y); + } - rect.x = thumbnailMain[0]; - rect.y = thumbnailMain[1]; + private _clear(): void { + this.group.removeAll(); + this._thumbnailController && this._thumbnailController.disable(); + } - rect.shape.width = newWidth; - rect.shape.height = newHeight; + remove() { + this._clear(); + } - if (min === false) { - rect.dirty(); - } - }; - const rect = this._selectedRect; + dispose() { + this._clear(); + } - const {x: rMinX, y: rMinY, shape: {width: rWidth, height: rHeight}} = rect; - const {x: wMinX, y: wMinY, shape: {width: wWidth, height: wHeight}} = this._wrapper; + static defaultOption: ThumbnailOption = { + show: false, - const [rMaxX, rMaxY] = [rMinX + rWidth, rMinY + rHeight]; - const [wMaxX, wMaxY] = [wMinX + wWidth, wMinY + wHeight]; + right: 0, + bottom: 0, - if (type === 'init') { - rect.show(); - getNewRect(); - return; - } - else if (type === 'zoom' && rWidth < wWidth / 10) { - getNewRect(true); - return; - } - if (rMinX > wMinX && rMinY > wMinY && rMaxX < wMaxX && rMaxY < wMaxY) { - this._selectedRect.show(); - // this._selectedRect.removeClipPath(); - } - else { - // this._selectedRect.removeClipPath(); - // this._selectedRect.setClipPath(this._wrapper); - this._selectedRect.hide(); - } + height: '25%', + width: '25%', - getNewRect(); - } + itemStyle: { + // Use echarts option.backgorundColor by default. + borderColor: '#555', + borderWidth: 2 + }, - _handleThumbnailShape(size: number | string, api: ExtensionAPI, type: 'height' | 'width') { - if (typeof size === 'number') { - return size; - } - else { - const len = size.length; - if (size.includes('%') && size.indexOf('%') === len - 1) { - const screenSize = type === 'height' ? api.getHeight() : api.getWidth(); - return +size.slice(0, len - 1) * screenSize / 100; - } - return 200; + windowStyle: { + borderWidth: 1, + color: 'green', + borderColor: '#000', + opacity: 0.3 } - } + }; +} - remove() { - this.group.removeAll(); - } +// TODO: other coordinate system. +function isSeriesSupported(seriesModel: SeriesModel): boolean { + const seriesCoordSys = seriesModel.coordinateSystem; + return seriesCoordSys && seriesCoordSys.type === 'view'; } export default Thumbnail; \ No newline at end of file diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index bd6a4ecc6e..be32e5887b 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -25,7 +25,7 @@ import {radialCoordinate} from './layoutHelper'; import * as bbox from 'zrender/src/core/bbox'; import View from '../../coord/View'; import * as roamHelper from '../../component/helper/roamHelper'; -import RoamController, { RoamControllerHost } from '../../component/helper/RoamController'; +import RoamController from '../../component/helper/RoamController'; import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import {parsePercent} from '../../util/number'; import ChartView from '../../view/Chart'; @@ -132,7 +132,7 @@ class TreeView extends ChartView { private _mainGroup = new graphic.Group(); private _controller: RoamController; - private _controllerHost: RoamControllerHost; + private _controllerHost: roamHelper.RoamControllerHost; private _data: SeriesData; @@ -145,7 +145,7 @@ class TreeView extends ChartView { this._controllerHost = { target: this.group - } as RoamControllerHost; + } as roamHelper.RoamControllerHost; this.group.add(this._mainGroup); } diff --git a/src/component/helper/RoamController.ts b/src/component/helper/RoamController.ts index 874865ebee..e316edb32a 100644 --- a/src/component/helper/RoamController.ts +++ b/src/component/helper/RoamController.ts @@ -23,7 +23,6 @@ import * as interactionMutex from './interactionMutex'; import { ZRenderType } from 'zrender/src/zrender'; import { ZRElementEvent, RoamOptionMixin } from '../../util/types'; import { Bind3, isString, bind, defaults, clone } from 'zrender/src/core/util'; -import Group from 'zrender/src/graphic/Group'; // Can be null/undefined or true/false // or 'pan/move' or 'zoom'/'scale' @@ -69,19 +68,11 @@ export interface RoamEventParams { isAvailableBehavior: Bind3 } }; - -export interface RoamControllerHost { - target: Group - zoom: number - zoomLimit: { - min?: number - max?: number - } -} - -class RoamController extends Eventful<{ +export type RoamEventDefinition = { [key in keyof RoamEventParams]: (params: RoamEventParams[key]) => void | undefined -}> { +}; + +class RoamController extends Eventful { pointerChecker: (e: ZRElementEvent, x: number, y: number) => boolean; diff --git a/src/component/helper/roamHelper.ts b/src/component/helper/roamHelper.ts index 07d5839607..0ed6b819fc 100644 --- a/src/component/helper/roamHelper.ts +++ b/src/component/helper/roamHelper.ts @@ -19,16 +19,16 @@ import Element from 'zrender/src/Element'; -interface ControllerHost { - target: Element, - zoom?: number - zoomLimit?: {min?: number, max?: number} +export interface RoamControllerHost { + target: Element; + zoom?: number; + zoomLimit?: {min?: number, max?: number}; } /** * For geo and graph. */ -export function updateViewOnPan(controllerHost: ControllerHost, dx: number, dy: number) { +export function updateViewOnPan(controllerHost: RoamControllerHost, dx: number, dy: number) { const target = controllerHost.target; target.x += dx; target.y += dy; @@ -38,7 +38,7 @@ export function updateViewOnPan(controllerHost: ControllerHost, dx: number, dy: /** * For geo and graph. */ -export function updateViewOnZoom(controllerHost: ControllerHost, zoomDelta: number, zoomX: number, zoomY: number) { +export function updateViewOnZoom(controllerHost: RoamControllerHost, zoomDelta: number, zoomX: number, zoomY: number) { const target = controllerHost.target; const zoomLimit = controllerHost.zoomLimit; diff --git a/src/util/layout.ts b/src/util/layout.ts index 1c30e72948..b9acfa051c 100644 --- a/src/util/layout.ts +++ b/src/util/layout.ts @@ -20,7 +20,7 @@ // Layout helpers for each component positioning import * as zrUtil from 'zrender/src/core/util'; -import BoundingRect from 'zrender/src/core/BoundingRect'; +import BoundingRect, { RectLike } from 'zrender/src/core/BoundingRect'; import {parsePercent} from './number'; import * as formatUtil from './format'; import { BoxLayoutOptionMixin, ComponentLayoutMode } from './types'; @@ -196,7 +196,22 @@ export function getLayoutRect( positionInfo: BoxLayoutOptionMixin & { aspect?: number // aspect is width / height }, - containerRect: {width: number, height: number}, + // The options in `positionInfo` is based on the container rect: + containerRect: { + x?: number; // by default 0 + y?: number; // by default 0 + width: number; // required + height: number; // required + }, + // This is the margin to the canvas. If width/height is specified, + // `margin` does not effect width/height. + // If using `margin`, we should make sure: + // either [A]: + // layout like CSS content-box, that is, user specified width/height means + // content width/height, which do not include border-width and pedding. + // or [B]: + // layout like CSS border-box, but user can not specify width/height + // (like in `title`/`tootbox` component did) margin?: number | number[] ): LayoutRect { margin = formatUtil.normalizeCssArray(margin || 0); @@ -289,6 +304,12 @@ export function getLayoutRect( const rect = new BoundingRect(left + margin[3], top + margin[0], width, height) as LayoutRect; rect.margin = margin; + if (containerRect.x) { + rect.x += containerRect.x; + } + if (containerRect.y) { + rect.y += containerRect.y; + } return rect; } @@ -546,3 +567,15 @@ export function copyLayoutParams(target: BoxLayoutOptionMixin, source: BoxLayout }); return target; } + +/** + * Apply pedding (CSS like) to a rect, and return the input rect. + */ +export function applyPedding(rect: TRect, pedding?: number | number[]): TRect { + const peddingArr = formatUtil.normalizeCssArray(pedding || 0); + rect.x += peddingArr[3]; + rect.y += peddingArr[0]; + rect.width -= peddingArr[1] + peddingArr[3]; + rect.height -= peddingArr[0] + peddingArr[2]; + return rect; +} diff --git a/test/graph-thumbnail.html b/test/graph-thumbnail.html index aa19a6813e..86a17840bf 100644 --- a/test/graph-thumbnail.html +++ b/test/graph-thumbnail.html @@ -13,18 +13,20 @@ +
-
+
+ + + +