diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts index a91f907b27..c9b215bfec 100644 --- a/src/chart/sankey/SankeySeries.ts +++ b/src/chart/sankey/SankeySeries.ts @@ -34,12 +34,14 @@ import { GraphEdgeItemObject, OptionDataValueNumeric, DefaultEmphasisFocus, - CallbackDataParams + CallbackDataParams, + RoamOptionMixin } from '../../util/types'; import GlobalModel from '../../model/Global'; import SeriesData from '../../data/SeriesData'; import { LayoutRect } from '../../util/layout'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; +import type View from '../../coord/View'; type FocusNodeAdjacency = boolean | 'inEdges' | 'outEdges' | 'allEdges'; @@ -95,7 +97,8 @@ export interface SankeyLevelOption extends SankeyNodeStateOption, SankeyEdgeStat export interface SankeySeriesOption extends SeriesOption, ExtraStateOption>, SankeyBothStateOption, - BoxLayoutOptionMixin { + BoxLayoutOptionMixin, + RoamOptionMixin { type?: 'sankey' /** @@ -148,6 +151,8 @@ class SankeySeriesModel extends SeriesModel { static readonly type = 'series.sankey'; readonly type = SankeySeriesModel.type; + coordinateSystem: View; + levelModels: Model[]; layoutInfo: LayoutRect; @@ -213,6 +218,14 @@ class SankeySeriesModel extends SeriesModel { dataItem.localY = localPosition[1]; } + setCenter(center: number[]) { + this.option.center = center; + } + + setZoom(zoom: number) { + this.option.zoom = zoom; + } + /** * Return the graphic data structure * @@ -297,6 +310,11 @@ class SankeySeriesModel extends SeriesModel { layoutIterations: 32, + // true | false | 'move' | 'scale', see module:component/helper/RoamController. + roam: false, + center: null, + zoom: 1, + label: { show: true, position: 'right', diff --git a/src/chart/sankey/SankeyView.ts b/src/chart/sankey/SankeyView.ts index e84b7f7ee8..eb78454852 100644 --- a/src/chart/sankey/SankeyView.ts +++ b/src/chart/sankey/SankeyView.ts @@ -31,6 +31,9 @@ import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; import { getECData } from '../../util/innerStore'; import { isString, retrieve3 } from 'zrender/src/core/util'; import type { GraphEdge } from '../../data/Graph'; +import RoamController from '../../component/helper/RoamController'; +import * as roamHelper from '../../component/helper/roamHelper'; +import View from '../../coord/View'; class SankeyPathShape { x1 = 0; @@ -106,15 +109,28 @@ class SankeyView extends ChartView { readonly type = SankeyView.type; private _model: SankeySeriesModel; - + private _mainGroup = new graphic.Group(); private _focusAdjacencyDisabled = false; private _data: SeriesData; + private _controller: RoamController; + private _controllerHost: roamHelper.RoamControllerHost; + + init(ecModel: GlobalModel, api: ExtensionAPI): void { + this._controller = new RoamController(api.getZr()); + + this._controllerHost = { + target: this.group + } as roamHelper.RoamControllerHost; + + this.group.add(this._mainGroup); + } + render(seriesModel: SankeySeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { const sankeyView = this; const graph = seriesModel.getGraph(); - const group = this.group; + const group = this._mainGroup; const layoutInfo = seriesModel.layoutInfo; // view width const width = layoutInfo.width; @@ -128,8 +144,8 @@ class SankeyView extends ChartView { group.removeAll(); - group.x = layoutInfo.x; - group.y = layoutInfo.y; + this._updateViewCoordSys(seriesModel, api); + roamHelper.updateController(seriesModel, api, group, this._controller, this._controllerHost); // generate a bezire Curve for each edge graph.eachEdge(function (edge) { @@ -346,6 +362,25 @@ class SankeyView extends ChartView { } dispose() { + this._controller && this._controller.dispose(); + this._controllerHost = null; + } + + private _updateViewCoordSys(seriesModel: SankeySeriesModel, api: ExtensionAPI) { + const layoutInfo = seriesModel.layoutInfo; + const width = layoutInfo.width; + const height = layoutInfo.height; + + const viewCoordSys = seriesModel.coordinateSystem = new View(); + viewCoordSys.zoomLimit = seriesModel.get('scaleLimit'); + + viewCoordSys.setBoundingRect(0, 0, width, height); + + viewCoordSys.setCenter(seriesModel.get('center'), api); + viewCoordSys.setZoom(seriesModel.get('zoom')); + + this._mainGroup.x = layoutInfo.x; + this._mainGroup.y = layoutInfo.y; } } diff --git a/src/chart/sankey/install.ts b/src/chart/sankey/install.ts index b4f2fd493d..b3da093bfb 100644 --- a/src/chart/sankey/install.ts +++ b/src/chart/sankey/install.ts @@ -25,6 +25,8 @@ import sankeyLayout from './sankeyLayout'; import sankeyVisual from './sankeyVisual'; import { Payload } from '../../util/types'; import GlobalModel from '../../model/Global'; +import { updateCenterAndZoom, RoamPayload } from '../../action/roamHelper'; +import type ExtensionAPI from '../../core/ExtensionAPI'; interface SankeyDragNodePayload extends Payload { localX: number @@ -53,4 +55,21 @@ export function install(registers: EChartsExtensionInstallRegisters) { }); }); -} \ No newline at end of file + registers.registerAction({ + type: 'sankeyRoam', + event: 'sankeyRoam', + update: 'none' + }, function (payload: RoamPayload, ecModel: GlobalModel, api: ExtensionAPI) { + ecModel.eachComponent({ + mainType: 'series', subType: 'sankey', query: payload + }, function (seriesModel: SankeySeriesModel) { + const coordSys = seriesModel.coordinateSystem; + const res = updateCenterAndZoom(coordSys, payload, undefined, api); + + seriesModel.setCenter + && seriesModel.setCenter(res.center); + seriesModel.setZoom + && seriesModel.setZoom(res.zoom); + }); + }); +} diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index be32e5887b..62dfbba541 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -26,7 +26,6 @@ import * as bbox from 'zrender/src/core/bbox'; import View from '../../coord/View'; import * as roamHelper from '../../component/helper/roamHelper'; import RoamController from '../../component/helper/RoamController'; -import {onIrrelevantElement} from '../../component/helper/cursorHelper'; import {parsePercent} from '../../util/number'; import ChartView from '../../view/Chart'; import TreeSeriesModel, { TreeSeriesOption, TreeSeriesNodeItemOption } from './TreeSeries'; @@ -277,44 +276,17 @@ class TreeView extends ChartView { ecModel: GlobalModel, api: ExtensionAPI ) { - const controller = this._controller; - const controllerHost = this._controllerHost; - const group = this.group; - controller.setPointerChecker(function (e, x, y) { - const rect = group.getBoundingRect(); - rect.applyTransform(group.transform); - return rect.contain(x, y) - && !onIrrelevantElement(e, api, seriesModel); - }); - - controller.enable(seriesModel.get('roam')); - controllerHost.zoomLimit = seriesModel.get('scaleLimit'); - controllerHost.zoom = seriesModel.coordinateSystem.getZoom(); - - controller - .off('pan') - .off('zoom') - .on('pan', (e) => { - roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy); - api.dispatchAction({ - seriesId: seriesModel.id, - type: 'treeRoam', - dx: e.dx, - dy: e.dy - }); - }) + roamHelper.updateController( + seriesModel, + api, + this.group, + this._controller, + this._controllerHost + ); + + this._controller .on('zoom', (e) => { - roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); - api.dispatchAction({ - seriesId: seriesModel.id, - type: 'treeRoam', - zoom: e.scale, - originX: e.originX, - originY: e.originY - }); this._updateNodeAndLinkScale(seriesModel); - // Only update label layout on zoom - api.updateLabelLayout(); }); } diff --git a/src/component/helper/roamHelper.ts b/src/component/helper/roamHelper.ts index 0ed6b819fc..8f87a9e001 100644 --- a/src/component/helper/roamHelper.ts +++ b/src/component/helper/roamHelper.ts @@ -18,6 +18,14 @@ */ import Element from 'zrender/src/Element'; +import { SeriesModel } from '../../echarts.all'; +import ExtensionAPI from '../../core/ExtensionAPI'; +import Group from 'zrender/src/graphic/Group'; +import RoamController from './RoamController'; +import type { SeriesOption } from '../../export/option'; +import type View from '../../coord/View'; +import type { RoamOptionMixin } from '../../util/types'; +import { onIrrelevantElement } from './cursorHelper'; export interface RoamControllerHost { target: Element; @@ -62,3 +70,49 @@ export function updateViewOnZoom(controllerHost: RoamControllerHost, zoomDelta: target.dirty(); } + +export function updateController( + seriesModel: SeriesModel, + api: ExtensionAPI, + group: Group, + controller: RoamController, + controllerHost: RoamControllerHost, +) { + controller.setPointerChecker(function (e, x, y) { + const rect = group.getBoundingRect(); + rect.applyTransform(group.transform); + return rect.contain(x, y) + && !onIrrelevantElement(e, api, seriesModel); + }); + + controller.enable(seriesModel.get('roam')); + controllerHost.zoomLimit = seriesModel.get('scaleLimit'); + const coordinate = seriesModel.coordinateSystem; + controllerHost.zoom = coordinate ? (coordinate as View).getZoom() : 1; + const type = seriesModel.type + 'Roam'; + + controller + .off('pan') + .off('zoom') + .on('pan', (e) => { + updateViewOnPan(controllerHost, e.dx, e.dy); + api.dispatchAction({ + seriesId: seriesModel.id, + type, + dx: e.dx, + dy: e.dy + }); + }) + .on('zoom', (e) => { + updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY); + api.dispatchAction({ + seriesId: seriesModel.id, + type, + zoom: e.scale, + originX: e.originX, + originY: e.originY + }); + // Only update label layout on zoom + api.updateLabelLayout(); + }); +} diff --git a/test/sankey-roam.html b/test/sankey-roam.html new file mode 100644 index 0000000000..259d7b04d0 --- /dev/null +++ b/test/sankey-roam.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +