Skip to content

(feat) Sankey roaming #20321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/chart/sankey/SankeySeries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -95,7 +97,8 @@ export interface SankeyLevelOption extends SankeyNodeStateOption, SankeyEdgeStat
export interface SankeySeriesOption
extends SeriesOption<SankeyBothStateOption<CallbackDataParams>, ExtraStateOption>,
SankeyBothStateOption<CallbackDataParams>,
BoxLayoutOptionMixin {
BoxLayoutOptionMixin,
RoamOptionMixin {
type?: 'sankey'

/**
Expand Down Expand Up @@ -148,6 +151,8 @@ class SankeySeriesModel extends SeriesModel<SankeySeriesOption> {
static readonly type = 'series.sankey';
readonly type = SankeySeriesModel.type;

coordinateSystem: View;

levelModels: Model<SankeyLevelOption>[];

layoutInfo: LayoutRect;
Expand Down Expand Up @@ -213,6 +218,14 @@ class SankeySeriesModel extends SeriesModel<SankeySeriesOption> {
dataItem.localY = localPosition[1];
}

setCenter(center: number[]) {
this.option.center = center;
}

setZoom(zoom: number) {
this.option.zoom = zoom;
}

/**
* Return the graphic data structure
*
Expand Down Expand Up @@ -297,6 +310,11 @@ class SankeySeriesModel extends SeriesModel<SankeySeriesOption> {

layoutIterations: 32,

// true | false | 'move' | 'scale', see module:component/helper/RoamController.
roam: false,
center: null,
zoom: 1,

label: {
show: true,
position: 'right',
Expand Down
43 changes: 39 additions & 4 deletions src/chart/sankey/SankeyView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
}

Expand Down
21 changes: 20 additions & 1 deletion src/chart/sankey/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,4 +55,21 @@ export function install(registers: EChartsExtensionInstallRegisters) {
});
});

}
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);
});
});
}
46 changes: 9 additions & 37 deletions src/chart/tree/TreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
});
}

Expand Down
54 changes: 54 additions & 0 deletions src/component/helper/roamHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,3 +70,49 @@ export function updateViewOnZoom(controllerHost: RoamControllerHost, zoomDelta:

target.dirty();
}

export function updateController(
seriesModel: SeriesModel<SeriesOption & RoamOptionMixin>,
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();
});
}
Loading