/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
import * as zrUtil from 'zrender/src/core/util';
|
import * as graphic from '../../util/graphic';
|
import {getECData} from '../../util/innerStore';
|
import SymbolClz from '../helper/Symbol';
|
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 {onIrrelevantElement} from '../../component/helper/cursorHelper';
|
import {parsePercent} from '../../util/number';
|
import ChartView from '../../view/Chart';
|
import TreeSeriesModel, { TreeSeriesOption, TreeSeriesNodeItemOption } from './TreeSeries';
|
import Path, { PathProps } from 'zrender/src/graphic/Path';
|
import GlobalModel from '../../model/Global';
|
import ExtensionAPI from '../../core/ExtensionAPI';
|
import { TreeNode } from '../../data/Tree';
|
import List from '../../data/List';
|
import { setStatesStylesFromModel, setStatesFlag, setDefaultStateProxy, HOVER_STATE_BLUR } from '../../util/states';
|
import { ECElement } from '../../util/types';
|
|
type TreeSymbol = SymbolClz & {
|
__edge: graphic.BezierCurve | TreePath
|
|
__radialOldRawX: number
|
__radialOldRawY: number
|
__radialRawX: number
|
__radialRawY: number
|
|
__oldX: number
|
__oldY: number
|
};
|
|
class TreeEdgeShape {
|
parentPoint: number[] = [];
|
childPoints: number[][] = [];
|
orient: TreeSeriesOption['orient'];
|
forkPosition: TreeSeriesOption['edgeForkPosition'];
|
}
|
|
interface TreeEdgePathProps extends PathProps {
|
shape?: Partial<TreeEdgeShape>
|
}
|
|
interface TreeNodeLayout {
|
x: number
|
y: number
|
rawX: number
|
rawY: number
|
}
|
|
class TreePath extends Path<TreeEdgePathProps> {
|
shape: TreeEdgeShape;
|
constructor(opts?: TreeEdgePathProps) {
|
super(opts);
|
}
|
|
getDefaultStyle() {
|
return {
|
stroke: '#000',
|
fill: null as string
|
};
|
}
|
|
getDefaultShape() {
|
return new TreeEdgeShape();
|
}
|
|
buildPath(ctx: CanvasRenderingContext2D, shape: TreeEdgeShape) {
|
const childPoints = shape.childPoints;
|
const childLen = childPoints.length;
|
const parentPoint = shape.parentPoint;
|
const firstChildPos = childPoints[0];
|
const lastChildPos = childPoints[childLen - 1];
|
|
if (childLen === 1) {
|
ctx.moveTo(parentPoint[0], parentPoint[1]);
|
ctx.lineTo(firstChildPos[0], firstChildPos[1]);
|
return;
|
}
|
|
const orient = shape.orient;
|
const forkDim = (orient === 'TB' || orient === 'BT') ? 0 : 1;
|
const otherDim = 1 - forkDim;
|
const forkPosition = parsePercent(shape.forkPosition, 1);
|
const tmpPoint = [];
|
tmpPoint[forkDim] = parentPoint[forkDim];
|
tmpPoint[otherDim] = parentPoint[otherDim] + (lastChildPos[otherDim] - parentPoint[otherDim]) * forkPosition;
|
|
ctx.moveTo(parentPoint[0], parentPoint[1]);
|
ctx.lineTo(tmpPoint[0], tmpPoint[1]);
|
ctx.moveTo(firstChildPos[0], firstChildPos[1]);
|
tmpPoint[forkDim] = firstChildPos[forkDim];
|
ctx.lineTo(tmpPoint[0], tmpPoint[1]);
|
tmpPoint[forkDim] = lastChildPos[forkDim];
|
ctx.lineTo(tmpPoint[0], tmpPoint[1]);
|
ctx.lineTo(lastChildPos[0], lastChildPos[1]);
|
|
for (let i = 1; i < childLen - 1; i++) {
|
const point = childPoints[i];
|
ctx.moveTo(point[0], point[1]);
|
tmpPoint[forkDim] = point[forkDim];
|
ctx.lineTo(tmpPoint[0], tmpPoint[1]);
|
}
|
}
|
}
|
|
class TreeView extends ChartView {
|
|
static readonly type = 'tree';
|
readonly type = TreeView.type;
|
|
private _mainGroup = new graphic.Group();
|
|
private _controller: RoamController;
|
private _controllerHost: RoamControllerHost;
|
|
private _data: List<TreeSeriesModel>;
|
|
private _nodeScaleRatio: number;
|
private _min: number[];
|
private _max: number[];
|
|
init(ecModel: GlobalModel, api: ExtensionAPI) {
|
|
|
this._controller = new RoamController(api.getZr());
|
|
this._controllerHost = {
|
target: this.group
|
} as RoamControllerHost;
|
|
this.group.add(this._mainGroup);
|
}
|
|
render(
|
seriesModel: TreeSeriesModel,
|
ecModel: GlobalModel,
|
api: ExtensionAPI
|
) {
|
const data = seriesModel.getData();
|
|
const layoutInfo = seriesModel.layoutInfo;
|
|
const group = this._mainGroup;
|
|
const layout = seriesModel.get('layout');
|
|
if (layout === 'radial') {
|
group.x = layoutInfo.x + layoutInfo.width / 2;
|
group.y = layoutInfo.y + layoutInfo.height / 2;
|
}
|
else {
|
group.x = layoutInfo.x;
|
group.y = layoutInfo.y;
|
}
|
|
this._updateViewCoordSys(seriesModel);
|
this._updateController(seriesModel, ecModel, api);
|
|
const oldData = this._data;
|
|
data.diff(oldData)
|
.add(function (newIdx) {
|
if (symbolNeedsDraw(data, newIdx)) {
|
// Create node and edge
|
updateNode(data, newIdx, null, group, seriesModel);
|
}
|
})
|
.update(function (newIdx, oldIdx) {
|
const symbolEl = oldData.getItemGraphicEl(oldIdx) as TreeSymbol;
|
if (!symbolNeedsDraw(data, newIdx)) {
|
symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel);
|
return;
|
}
|
// Update node and edge
|
updateNode(data, newIdx, symbolEl, group, seriesModel);
|
})
|
.remove(function (oldIdx) {
|
const symbolEl = oldData.getItemGraphicEl(oldIdx) as TreeSymbol;
|
// When remove a collapsed node of subtree, since the collapsed
|
// node haven't been initialized with a symbol element,
|
// you can't found it's symbol element through index.
|
// so if we want to remove the symbol element we should insure
|
// that the symbol element is not null.
|
if (symbolEl) {
|
removeNode(oldData, oldIdx, symbolEl, group, seriesModel);
|
}
|
})
|
.execute();
|
|
this._nodeScaleRatio = seriesModel.get('nodeScaleRatio');
|
|
this._updateNodeAndLinkScale(seriesModel);
|
|
if (seriesModel.get('expandAndCollapse') === true) {
|
data.eachItemGraphicEl(function (el, dataIndex) {
|
el.off('click').on('click', function () {
|
api.dispatchAction({
|
type: 'treeExpandAndCollapse',
|
seriesId: seriesModel.id,
|
dataIndex: dataIndex
|
});
|
});
|
});
|
}
|
this._data = data;
|
}
|
|
_updateViewCoordSys(seriesModel: TreeSeriesModel) {
|
const data = seriesModel.getData();
|
const points: number[][] = [];
|
data.each(function (idx) {
|
const layout = data.getItemLayout(idx);
|
if (layout && !isNaN(layout.x) && !isNaN(layout.y)) {
|
points.push([+layout.x, +layout.y]);
|
}
|
});
|
const min: number[] = [];
|
const max: number[] = [];
|
bbox.fromPoints(points, min, max);
|
|
// If don't Store min max when collapse the root node after roam,
|
// the root node will disappear.
|
const oldMin = this._min;
|
const oldMax = this._max;
|
|
// If width or height is 0
|
if (max[0] - min[0] === 0) {
|
min[0] = oldMin ? oldMin[0] : min[0] - 1;
|
max[0] = oldMax ? oldMax[0] : max[0] + 1;
|
}
|
if (max[1] - min[1] === 0) {
|
min[1] = oldMin ? oldMin[1] : min[1] - 1;
|
max[1] = oldMax ? oldMax[1] : max[1] + 1;
|
}
|
|
const viewCoordSys = seriesModel.coordinateSystem = new View();
|
viewCoordSys.zoomLimit = seriesModel.get('scaleLimit');
|
|
viewCoordSys.setBoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]);
|
|
viewCoordSys.setCenter(seriesModel.get('center'));
|
viewCoordSys.setZoom(seriesModel.get('zoom'));
|
|
// Here we use viewCoordSys just for computing the 'position' and 'scale' of the group
|
this.group.attr({
|
x: viewCoordSys.x,
|
y: viewCoordSys.y,
|
scaleX: viewCoordSys.scaleX,
|
scaleY: viewCoordSys.scaleY
|
});
|
|
this._min = min;
|
this._max = max;
|
}
|
|
_updateController(
|
seriesModel: TreeSeriesModel,
|
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
|
});
|
})
|
.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();
|
});
|
}
|
|
_updateNodeAndLinkScale(seriesModel: TreeSeriesModel) {
|
const data = seriesModel.getData();
|
|
const nodeScale = this._getNodeGlobalScale(seriesModel);
|
|
data.eachItemGraphicEl(function (el: SymbolClz, idx) {
|
el.setSymbolScale(nodeScale);
|
});
|
}
|
|
_getNodeGlobalScale(seriesModel: TreeSeriesModel) {
|
const coordSys = seriesModel.coordinateSystem;
|
if (coordSys.type !== 'view') {
|
return 1;
|
}
|
|
const nodeScaleRatio = this._nodeScaleRatio;
|
|
const groupZoom = coordSys.scaleX || 1;
|
// Scale node when zoom changes
|
const roamZoom = coordSys.getZoom();
|
const nodeScale = (roamZoom - 1) * nodeScaleRatio + 1;
|
|
return nodeScale / groupZoom;
|
}
|
|
dispose() {
|
this._controller && this._controller.dispose();
|
this._controllerHost = null;
|
}
|
|
remove() {
|
this._mainGroup.removeAll();
|
this._data = null;
|
}
|
|
}
|
|
function symbolNeedsDraw(data: List, dataIndex: number) {
|
const layout = data.getItemLayout(dataIndex);
|
|
return layout
|
&& !isNaN(layout.x) && !isNaN(layout.y);
|
}
|
|
|
function updateNode(
|
data: List,
|
dataIndex: number,
|
symbolEl: TreeSymbol,
|
group: graphic.Group,
|
seriesModel: TreeSeriesModel
|
) {
|
const isInit = !symbolEl;
|
const node = data.tree.getNodeByDataIndex(dataIndex);
|
const itemModel = node.getModel<TreeSeriesNodeItemOption>();
|
const visualColor = node.getVisual('style').fill;
|
const symbolInnerColor = node.isExpand === false && node.children.length !== 0
|
? visualColor : '#fff';
|
|
const virtualRoot = data.tree.root;
|
|
const source = node.parentNode === virtualRoot ? node : node.parentNode || node;
|
const sourceSymbolEl = data.getItemGraphicEl(source.dataIndex) as TreeSymbol;
|
const sourceLayout = source.getLayout() as TreeNodeLayout;
|
const sourceOldLayout = sourceSymbolEl
|
? {
|
x: sourceSymbolEl.__oldX,
|
y: sourceSymbolEl.__oldY,
|
rawX: sourceSymbolEl.__radialOldRawX,
|
rawY: sourceSymbolEl.__radialOldRawY
|
}
|
: sourceLayout;
|
const targetLayout = node.getLayout();
|
|
if (isInit) {
|
symbolEl = new SymbolClz(data, dataIndex, null, {
|
symbolInnerColor,
|
useNameLabel: true
|
}) as TreeSymbol;
|
symbolEl.x = sourceOldLayout.x;
|
symbolEl.y = sourceOldLayout.y;
|
}
|
else {
|
symbolEl.updateData(data, dataIndex, null, {
|
symbolInnerColor,
|
useNameLabel: true
|
});
|
}
|
|
symbolEl.__radialOldRawX = symbolEl.__radialRawX;
|
symbolEl.__radialOldRawY = symbolEl.__radialRawY;
|
symbolEl.__radialRawX = targetLayout.rawX;
|
symbolEl.__radialRawY = targetLayout.rawY;
|
|
group.add(symbolEl);
|
data.setItemGraphicEl(dataIndex, symbolEl);
|
|
symbolEl.__oldX = symbolEl.x;
|
symbolEl.__oldY = symbolEl.y;
|
|
graphic.updateProps(symbolEl, {
|
x: targetLayout.x,
|
y: targetLayout.y
|
}, seriesModel);
|
|
const symbolPath = symbolEl.getSymbolPath();
|
|
if (seriesModel.get('layout') === 'radial') {
|
const realRoot = virtualRoot.children[0];
|
const rootLayout = realRoot.getLayout();
|
const length = realRoot.children.length;
|
let rad;
|
let isLeft;
|
|
if (targetLayout.x === rootLayout.x && node.isExpand === true) {
|
const center = {
|
x: (realRoot.children[0].getLayout().x + realRoot.children[length - 1].getLayout().x) / 2,
|
y: (realRoot.children[0].getLayout().y + realRoot.children[length - 1].getLayout().y) / 2
|
};
|
rad = Math.atan2(center.y - rootLayout.y, center.x - rootLayout.x);
|
if (rad < 0) {
|
rad = Math.PI * 2 + rad;
|
}
|
isLeft = center.x < rootLayout.x;
|
if (isLeft) {
|
rad = rad - Math.PI;
|
}
|
}
|
else {
|
rad = Math.atan2(targetLayout.y - rootLayout.y, targetLayout.x - rootLayout.x);
|
if (rad < 0) {
|
rad = Math.PI * 2 + rad;
|
}
|
if (node.children.length === 0 || (node.children.length !== 0 && node.isExpand === false)) {
|
isLeft = targetLayout.x < rootLayout.x;
|
if (isLeft) {
|
rad = rad - Math.PI;
|
}
|
}
|
else {
|
isLeft = targetLayout.x > rootLayout.x;
|
if (!isLeft) {
|
rad = rad - Math.PI;
|
}
|
}
|
}
|
|
const textPosition = isLeft ? 'left' as const : 'right' as const;
|
const normalLabelModel = itemModel.getModel('label');
|
const rotate = normalLabelModel.get('rotate');
|
const labelRotateRadian = rotate * (Math.PI / 180);
|
|
const textContent = symbolPath.getTextContent();
|
if (textContent) {
|
symbolPath.setTextConfig({
|
position: normalLabelModel.get('position') || textPosition,
|
rotation: rotate == null ? -rad : labelRotateRadian,
|
origin: 'center'
|
});
|
textContent.setStyle('verticalAlign', 'middle');
|
}
|
|
}
|
|
// Handle status
|
const focus = itemModel.get(['emphasis', 'focus']);
|
const focusDataIndices: number[] = focus === 'ancestor'
|
? node.getAncestorsIndices()
|
: focus === 'descendant' ? node.getDescendantIndices() : null;
|
|
if (focusDataIndices) {
|
// Modify the focus to data indices.
|
getECData(symbolEl).focus = focusDataIndices;
|
}
|
|
drawEdge(
|
seriesModel, node, virtualRoot, symbolEl, sourceOldLayout,
|
sourceLayout, targetLayout, group
|
);
|
|
if (symbolEl.__edge) {
|
(symbolEl as ECElement).onHoverStateChange = function (toState) {
|
if (toState !== 'blur') {
|
// NOTE: Ensure the parent elements will been blurred firstly.
|
// According to the return of getAncestorsIndices and getDescendantIndices
|
// TODO: A bit tricky.
|
const parentEl = node.parentNode
|
&& data.getItemGraphicEl(node.parentNode.dataIndex);
|
if (!(parentEl && (parentEl as ECElement).hoverState === HOVER_STATE_BLUR)) {
|
setStatesFlag(symbolEl.__edge, toState);
|
}
|
}
|
};
|
}
|
}
|
|
function drawEdge(
|
seriesModel: TreeSeriesModel,
|
node: TreeNode,
|
virtualRoot: TreeNode,
|
symbolEl: TreeSymbol,
|
sourceOldLayout: TreeNodeLayout,
|
sourceLayout: TreeNodeLayout,
|
targetLayout: TreeNodeLayout,
|
group: graphic.Group
|
) {
|
const itemModel = node.getModel<TreeSeriesNodeItemOption>();
|
const edgeShape = seriesModel.get('edgeShape');
|
const layout = seriesModel.get('layout');
|
const orient = seriesModel.getOrient();
|
const curvature = seriesModel.get(['lineStyle', 'curveness']);
|
const edgeForkPosition = seriesModel.get('edgeForkPosition');
|
const lineStyle = itemModel.getModel('lineStyle').getLineStyle();
|
let edge = symbolEl.__edge;
|
if (edgeShape === 'curve') {
|
if (node.parentNode && node.parentNode !== virtualRoot) {
|
if (!edge) {
|
edge = symbolEl.__edge = new graphic.BezierCurve({
|
shape: getEdgeShape(layout, orient, curvature, sourceOldLayout, sourceOldLayout)
|
});
|
}
|
|
graphic.updateProps(edge as Path, {
|
shape: getEdgeShape(layout, orient, curvature, sourceLayout, targetLayout)
|
}, seriesModel);
|
}
|
}
|
else if (edgeShape === 'polyline') {
|
if (layout === 'orthogonal') {
|
if (node !== virtualRoot && node.children && (node.children.length !== 0) && (node.isExpand === true)) {
|
const children = node.children;
|
const childPoints = [];
|
for (let i = 0; i < children.length; i++) {
|
const childLayout = children[i].getLayout();
|
childPoints.push([childLayout.x, childLayout.y]);
|
}
|
|
if (!edge) {
|
edge = symbolEl.__edge = new TreePath({
|
shape: {
|
parentPoint: [targetLayout.x, targetLayout.y],
|
childPoints: [[targetLayout.x, targetLayout.y]],
|
orient: orient,
|
forkPosition: edgeForkPosition
|
}
|
});
|
}
|
graphic.updateProps(edge as Path, {
|
shape: {
|
parentPoint: [targetLayout.x, targetLayout.y],
|
childPoints: childPoints
|
}
|
}, seriesModel);
|
}
|
}
|
else {
|
if (__DEV__) {
|
throw new Error('The polyline edgeShape can only be used in orthogonal layout');
|
}
|
}
|
}
|
|
if (edge) {
|
edge.useStyle(zrUtil.defaults({
|
strokeNoScale: true, fill: null
|
}, lineStyle));
|
|
setStatesStylesFromModel(edge, itemModel, 'lineStyle');
|
setDefaultStateProxy(edge);
|
|
group.add(edge);
|
}
|
}
|
|
function removeNode(
|
data: List,
|
dataIndex: number,
|
symbolEl: TreeSymbol,
|
group: graphic.Group,
|
seriesModel: TreeSeriesModel
|
) {
|
const node = data.tree.getNodeByDataIndex(dataIndex);
|
const virtualRoot = data.tree.root;
|
|
let source = node.parentNode === virtualRoot ? node : node.parentNode || node;
|
// let edgeShape = seriesScope.edgeShape;
|
let sourceLayout;
|
while (sourceLayout = source.getLayout(), sourceLayout == null) {
|
source = source.parentNode === virtualRoot ? source : source.parentNode || source;
|
}
|
|
// Use same duration and easing with update to have more consistent animation.
|
const removeAnimationOpt = {
|
duration: seriesModel.get('animationDurationUpdate') as number,
|
easing: seriesModel.get('animationEasingUpdate')
|
};
|
|
graphic.removeElement(symbolEl, {
|
x: sourceLayout.x + 1,
|
y: sourceLayout.y + 1
|
}, seriesModel, {
|
cb() {
|
group.remove(symbolEl);
|
data.setItemGraphicEl(dataIndex, null);
|
},
|
removeOpt: removeAnimationOpt
|
});
|
|
symbolEl.fadeOut(null, {
|
fadeLabel: true,
|
animation: removeAnimationOpt
|
});
|
|
const sourceSymbolEl = data.getItemGraphicEl(source.dataIndex) as TreeSymbol;
|
const sourceEdge = sourceSymbolEl.__edge;
|
|
// 1. when expand the sub tree, delete the children node should delete the edge of
|
// the source at the same time. because the polyline edge shape is only owned by the source.
|
// 2.when the node is the only children of the source, delete the node should delete the edge of
|
// the source at the same time. the same reason as above.
|
const edge = symbolEl.__edge
|
|| ((source.isExpand === false || source.children.length === 1) ? sourceEdge : undefined);
|
|
const edgeShape = seriesModel.get('edgeShape');
|
const layoutOpt = seriesModel.get('layout');
|
const orient = seriesModel.get('orient');
|
const curvature = seriesModel.get(['lineStyle', 'curveness']);
|
|
if (edge) {
|
if (edgeShape === 'curve') {
|
graphic.removeElement(edge as Path, {
|
shape: getEdgeShape(
|
layoutOpt,
|
orient,
|
curvature,
|
sourceLayout,
|
sourceLayout
|
),
|
style: {
|
opacity: 0
|
}
|
}, seriesModel, {
|
cb() {
|
group.remove(edge);
|
},
|
removeOpt: removeAnimationOpt
|
});
|
}
|
else if (edgeShape === 'polyline' && seriesModel.get('layout') === 'orthogonal') {
|
graphic.removeElement(edge as Path, {
|
shape: {
|
parentPoint: [sourceLayout.x, sourceLayout.y],
|
childPoints: [[sourceLayout.x, sourceLayout.y]]
|
},
|
style: {
|
opacity: 0
|
}
|
}, seriesModel, {
|
cb() {
|
group.remove(edge);
|
},
|
removeOpt: removeAnimationOpt
|
});
|
}
|
}
|
}
|
|
function getEdgeShape(
|
layoutOpt: TreeSeriesOption['layout'],
|
orient: TreeSeriesOption['orient'],
|
curvature: number,
|
sourceLayout: TreeNodeLayout,
|
targetLayout: TreeNodeLayout
|
) {
|
let cpx1: number;
|
let cpy1: number;
|
let cpx2: number;
|
let cpy2: number;
|
let x1: number;
|
let x2: number;
|
let y1: number;
|
let y2: number;
|
|
if (layoutOpt === 'radial') {
|
x1 = sourceLayout.rawX;
|
y1 = sourceLayout.rawY;
|
x2 = targetLayout.rawX;
|
y2 = targetLayout.rawY;
|
|
const radialCoor1 = radialCoordinate(x1, y1);
|
const radialCoor2 = radialCoordinate(x1, y1 + (y2 - y1) * curvature);
|
const radialCoor3 = radialCoordinate(x2, y2 + (y1 - y2) * curvature);
|
const radialCoor4 = radialCoordinate(x2, y2);
|
|
return {
|
x1: radialCoor1.x || 0,
|
y1: radialCoor1.y || 0,
|
x2: radialCoor4.x || 0,
|
y2: radialCoor4.y || 0,
|
cpx1: radialCoor2.x || 0,
|
cpy1: radialCoor2.y || 0,
|
cpx2: radialCoor3.x || 0,
|
cpy2: radialCoor3.y || 0
|
};
|
}
|
else {
|
x1 = sourceLayout.x;
|
y1 = sourceLayout.y;
|
x2 = targetLayout.x;
|
y2 = targetLayout.y;
|
|
if (orient === 'LR' || orient === 'RL') {
|
cpx1 = x1 + (x2 - x1) * curvature;
|
cpy1 = y1;
|
cpx2 = x2 + (x1 - x2) * curvature;
|
cpy2 = y2;
|
}
|
if (orient === 'TB' || orient === 'BT') {
|
cpx1 = x1;
|
cpy1 = y1 + (y2 - y1) * curvature;
|
cpx2 = x2;
|
cpy2 = y2 + (y1 - y2) * curvature;
|
}
|
}
|
|
return {
|
x1: x1,
|
y1: y1,
|
x2: x2,
|
y2: y2,
|
cpx1: cpx1,
|
cpy1: cpy1,
|
cpx2: cpx2,
|
cpy2: cpy2
|
};
|
}
|
|
export default TreeView;
|