You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2021/09/23 18:32:46 UTC
[echarts] branch feat/HCLacids-NodeSelf-fix created (now 0566c7b)
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a change to branch feat/HCLacids-NodeSelf-fix
in repository https://gitbox.apache.org/repos/asf/echarts.git.
at 0566c7b fix: (1) Fix the self-loop edge layout strategy in 'simple' layout. (2) Add test case `test/graph-self-loop.html`. (3) Remove some necessary code.
This branch includes the following new commits:
new 4028577 Merge branch 'NodeSelf' of https://github.com/HCLacids/echarts into HCLacids-NodeSelf
new 52f292d Merge branch 'NodeSelf' of https://github.com/HCLacids/echarts into HCLacids-NodeSelf
new 6b6d6d4 Merge branch 'NodeSelf' of https://github.com/HCLacids/echarts into HCLacids-NodeSelf
new 0566c7b fix: (1) Fix the self-loop edge layout strategy in 'simple' layout. (2) Add test case `test/graph-self-loop.html`. (3) Remove some necessary code.
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org
[echarts] 03/04: Merge branch 'NodeSelf' of
https://github.com/HCLacids/echarts into HCLacids-NodeSelf
Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch feat/HCLacids-NodeSelf-fix
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit 6b6d6d45c41798103fb3f2926d5c2b66aeeb022b
Merge: 52f292d eee6878
Author: sushuang <su...@gmail.com>
AuthorDate: Wed Sep 15 19:59:43 2021 +0800
Merge branch 'NodeSelf' of https://github.com/HCLacids/echarts into HCLacids-NodeSelf
src/chart/graph/circularLayoutHelper.ts | 14 +++++------
src/chart/graph/forceLayout.ts | 20 ++++++++--------
src/chart/graph/simpleLayoutHelper.ts | 18 +++++++--------
src/chart/helper/multipleGraphEdgeHelper.ts | 36 ++++++++++++++---------------
4 files changed, 44 insertions(+), 44 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org
[echarts] 02/04: Merge branch 'NodeSelf' of
https://github.com/HCLacids/echarts into HCLacids-NodeSelf
Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch feat/HCLacids-NodeSelf-fix
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit 52f292d74c55d81ad8d5886c9222d818daf197fb
Merge: 4028577 9d44975
Author: sushuang <su...@gmail.com>
AuthorDate: Tue Aug 24 21:43:45 2021 +0800
Merge branch 'NodeSelf' of https://github.com/HCLacids/echarts into HCLacids-NodeSelf
package-lock.json | 10985 ++++++++++++++++++++++++++
src/chart/graph/adjustEdge.ts | 167 +-
src/chart/graph/circularLayoutHelper.ts | 74 +-
src/chart/graph/edgeVisual.ts | 24 +
src/chart/graph/forceLayout.ts | 74 +-
src/chart/graph/simpleLayoutHelper.ts | 106 +-
src/chart/helper/Line.ts | 5 +-
src/chart/helper/LinePath.ts | 4 +-
src/chart/helper/multipleGraphEdgeHelper.ts | 50 +-
test/force.html | 3 +-
test/graph-circular.html | 4 +
test/graph-multiple-edges.html | 2 +-
12 files changed, 11366 insertions(+), 132 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org
[echarts] 01/04: Merge branch 'NodeSelf' of
https://github.com/HCLacids/echarts into HCLacids-NodeSelf
Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch feat/HCLacids-NodeSelf-fix
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit 4028577c1670637ffb0e37f0dc72a553a3b4f7f0
Merge: 997f839 60e8a01
Author: sushuang <su...@gmail.com>
AuthorDate: Mon Aug 2 20:09:10 2021 +0800
Merge branch 'NodeSelf' of https://github.com/HCLacids/echarts into HCLacids-NodeSelf
package-lock.json | 10985 ------------------------------
src/chart/graph/adjustEdge.ts | 246 +-
src/chart/graph/circularLayoutHelper.ts | 13 +-
src/chart/graph/forceLayout.ts | 9 +-
src/chart/graph/simpleLayoutHelper.ts | 19 +-
src/chart/helper/Line.ts | 8 +
src/chart/helper/LinePath.ts | 2 +
test/force.html | 4 +-
test/graph-multiple-edges.html | 2 +-
test/graph-simple.html | 8 +
10 files changed, 227 insertions(+), 11069 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org
[echarts] 04/04: fix: (1) Fix the self-loop edge layout strategy in
'simple' layout. (2) Add test case `test/graph-self-loop.html`. (3) Remove
some necessary code.
Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch feat/HCLacids-NodeSelf-fix
in repository https://gitbox.apache.org/repos/asf/echarts.git
commit 0566c7b0d7ca8216adf0e197bf89f118d0a7d126
Author: sushuang <su...@gmail.com>
AuthorDate: Fri Sep 24 02:31:05 2021 +0800
fix:
(1) Fix the self-loop edge layout strategy in 'simple' layout.
(2) Add test case `test/graph-self-loop.html`.
(3) Remove some necessary code.
---
src/chart/graph/GraphView.ts | 20 +-
src/chart/graph/edgeVisual.ts | 24 --
src/chart/graph/graphHelper.ts | 6 +
src/chart/graph/layoutHelper.ts | 262 ++++++++++++++
src/chart/graph/simpleLayoutHelper.ts | 88 +----
src/chart/helper/multipleGraphEdgeHelper.ts | 3 +-
test/graph-self-loop.html | 355 +++++++++++++++++++
test/lib/enableGraphEditRoughly.js | 512 ++++++++++++++++++++++++++++
8 files changed, 1165 insertions(+), 105 deletions(-)
diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts
index 1c8d10f..98b5e3b 100644
--- a/src/chart/graph/GraphView.ts
+++ b/src/chart/graph/GraphView.ts
@@ -36,6 +36,7 @@ import Symbol from '../helper/Symbol';
import List from '../../data/List';
import Line from '../helper/Line';
import { getECData } from '../../util/innerStore';
+import { layoutSelfLoopEdges } from './layoutHelper';
function isViewCoordSys(coordSys: CoordinateSystem): coordSys is View {
return coordSys.type === 'view';
@@ -102,7 +103,7 @@ class GraphView extends ChartView {
}
}
// Fix edge contact point with node
- adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel));
+ postLayoutEdges(seriesModel);
const data = seriesModel.getData();
symbolDraw.updateData(data as ListForSymbolDraw);
@@ -274,7 +275,7 @@ class GraphView extends ChartView {
originY: e.originY
});
this._updateNodeAndLinkScale();
- adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel));
+ postLayoutEdges(seriesModel);
this._lineDraw.updateLayout();
// Only update label layout on zoom
api.updateLabelLayout();
@@ -293,7 +294,7 @@ class GraphView extends ChartView {
}
updateLayout(seriesModel: GraphSeriesModel) {
- adjustEdge(seriesModel.getGraph(), getNodeGlobalScale(seriesModel));
+ postLayoutEdges(seriesModel);
this._symbolDraw.updateLayout();
this._lineDraw.updateLayout();
@@ -305,4 +306,17 @@ class GraphView extends ChartView {
}
}
+function postLayoutEdges(seriesModel: GraphSeriesModel): void {
+ const graph = seriesModel.getGraph();
+
+ // PENDING:
+ // `scaleOnCoordSys` will be changed when zooming.
+ // At present the layout stage will not be called when zooming. So
+ // we put these process here.
+ const nodeScaleOnCoordSys = getNodeGlobalScale(seriesModel);
+
+ layoutSelfLoopEdges(graph, nodeScaleOnCoordSys);
+ adjustEdge(graph, nodeScaleOnCoordSys);
+}
+
export default GraphView;
\ No newline at end of file
diff --git a/src/chart/graph/edgeVisual.ts b/src/chart/graph/edgeVisual.ts
index f0d0208..7107399 100644
--- a/src/chart/graph/edgeVisual.ts
+++ b/src/chart/graph/edgeVisual.ts
@@ -20,9 +20,6 @@
import GlobalModel from '../../model/Global';
import GraphSeriesModel, { GraphEdgeItemOption } from './GraphSeries';
import { extend } from 'zrender/src/core/util';
-import { intersectCurveCircle } from './adjustEdge'
-import { getNodeGlobalScale, getSymbolSize } from './graphHelper';
-import { cubicDerivativeAt } from 'zrender/src/core/curve';
function normalize(a: string | string[]): string[];
function normalize(a: number | number[]): number[];
@@ -53,7 +50,6 @@ export default function graphEdgeVisual(ecModel: GlobalModel) {
edgeData.each(function (idx) {
const itemModel = edgeData.getItemModel<GraphEdgeItemOption>(idx);
const edge = graph.getEdgeByIndex(idx);
- const toSymbol = edge.getVisual('toSymbol');
const symbolType = normalize(itemModel.getShallow('symbol', true));
const symbolSize = normalize(itemModel.getShallow('symbolSize', true));
// Edge visual must after node visual
@@ -79,26 +75,6 @@ export default function graphEdgeVisual(ecModel: GlobalModel) {
symbolType[1] && edge.setVisual('toSymbol', symbolType[1]);
symbolSize[0] && edge.setVisual('fromSymbolSize', symbolSize[0]);
symbolSize[1] && edge.setVisual('toSymbolSize', symbolSize[1]);
-
-
- if (edge.node1 === edge.node2 && toSymbol && toSymbol !== 'none') {
- const edgeData = edge.getLayout();
- const size = getSymbolSize(edge.node1);
- const radius = getNodeGlobalScale(seriesModel) * size / 2;
-
- let t = intersectCurveCircle(edgeData, edgeData[0], radius);
- if (t < 0.5) {
- t = 1 - t;
- }
- const tdx = cubicDerivativeAt(edgeData[0][0], edgeData[1][0], edgeData[2][0], edgeData[3][0], t);
- const tdy = cubicDerivativeAt(edgeData[0][1], edgeData[1][1], edgeData[2][1], edgeData[3][1], t);
- const degree = Math.atan2(tdy, tdx) / Math.PI * 180;
- if( degree > 90 || degree < 0 && degree > -90) {
- edge.setVisual('toSymbolRotate', degree + 188);
- } else {
- edge.setVisual('toSymbolRotate', degree - 8);
- }
- }
});
});
}
\ No newline at end of file
diff --git a/src/chart/graph/graphHelper.ts b/src/chart/graph/graphHelper.ts
index 745a79b..6f03fb3 100644
--- a/src/chart/graph/graphHelper.ts
+++ b/src/chart/graph/graphHelper.ts
@@ -21,6 +21,12 @@ import GraphSeriesModel from './GraphSeries';
import View from '../../coord/View';
import { GraphNode } from '../../data/Graph';
+/**
+ * @return scale based on `View` coordinate system.
+ * The final displayed pixel size is
+ * `option.symbolSize * scale * view.getGlobalScale()`,
+ * which will be changed when zooming.
+ */
export function getNodeGlobalScale(seriesModel: GraphSeriesModel) {
const coordSys = seriesModel.coordinateSystem as View;
if (coordSys.type !== 'view') {
diff --git a/src/chart/graph/layoutHelper.ts b/src/chart/graph/layoutHelper.ts
new file mode 100644
index 0000000..cd1761b
--- /dev/null
+++ b/src/chart/graph/layoutHelper.ts
@@ -0,0 +1,262 @@
+/*
+* 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 Graph, { GraphEdge, GraphNode } from '../../data/Graph';
+import { sub, VectorArray } from 'zrender/src/core/vector';
+import { assert, bind, each, retrieve2 } from 'zrender/src/core/util';
+import { GraphEdgeItemOption } from './GraphSeries';
+import { getSymbolSize } from './graphHelper';
+
+
+type Radian = number;
+
+type NodeAttrOnEdge = 'node1' | 'node2';
+
+interface EdgeWrap {
+ /**
+ * Vector of the tangent line at the center node.
+ */
+ tangentVec: VectorArray;
+ /**
+ * Radian of tangentVec (to x positive) of tangentVec. [-Math.PI / 2, Math.PI / 2].
+ */
+ radToXPosi: Radian;
+}
+
+interface SectionWrap {
+ /**
+ * Radian of tangentVec (to x positive) of tangentVec. [-Math.PI / 2, Math.PI / 2].
+ * Make sure radToXPosiStart <= radToXPosiEnd.
+ */
+ radToXPosiStart: Radian;
+ radToXPosiEnd: Radian;
+
+ /**
+ * The count of edges that assign to this section.
+ */
+ edgeCount: number;
+}
+
+const MATH_PI = Math.PI;
+const MATH_2PI = MATH_PI * 2;
+
+/**
+ * This is the radian of the intersection angle of the two control
+ * point of a self-loop cubic bezier edge.
+ * If the angle is bigger or smaller, the cubic curve is not pretty.
+ */
+const MAX_EDGE_SECTION_RADIAN = MATH_PI - getRadianToXPositive([4, 5.5]) * 2;
+const MIN_EDGE_SECTION_RADIAN = MATH_PI / 3;
+
+
+/**
+ * @caution This method should only be called after all
+ * nodes and non-self-loop edges layout finished.
+ *
+ * @note [Self-loop edge layout strategy]:
+ * To make it have good looking when there are muliple self-loop edges,
+ * place them from the biggest angle (> 60 degree) one by one.
+ * If there is no enough angles, put mulitple edges in one angle
+ * and use different curvenesses.
+ *
+ * @pending Should the section angle and self-loop edge direction be able to set by user?
+ * `curveness` can not express it.
+ *
+ * @pending Consider the self-loop edge might overlow the canvas.
+ * When calculating the view transform, there is no self-loop layout info yet.
+ */
+export function layoutSelfLoopEdges(
+ graph: Graph,
+ // Get from `getNodeGlobalScale(seriesModel)`
+ nodeScaleOnCoordSys: number
+): void {
+ graph.eachNode(node => {
+ const selfLoopEdges: GraphEdge[] = [];
+ // inEdges includes outEdges if self-loop.
+ each(node.inEdges, edge => {
+ if (isSelfLoopEdge(edge)) {
+ selfLoopEdges.push(edge);
+ }
+ });
+
+ if (selfLoopEdges.length) {
+ const sectionList = prepareSectionList(node, selfLoopEdges.length);
+ placeSelfLoopEdges(node, sectionList, selfLoopEdges, nodeScaleOnCoordSys);
+ }
+ });
+}
+
+/**
+ * @return Sections that can arrange self-loop angles. Ensure that:
+ * `selfLoopEdgeCount <= sectionList.reduce((sum, sec) === sec.edgeCount + sum, 0)`
+ */
+function prepareSectionList(centerNode: GraphNode, selfLoopEdgeCount: number): SectionWrap[] {
+ const adjacentEdges: EdgeWrap[] = [];
+ function addAdjacentEdge(centerNodeAttr: NodeAttrOnEdge, edge: GraphEdge): void {
+ if (isSelfLoopEdge(edge)) {
+ return;
+ }
+ const tangentVec = getTangentVector(edge, centerNodeAttr);
+ const radToXPosi = getRadianToXPositive(tangentVec);
+ adjacentEdges.push({ tangentVec, radToXPosi });
+ }
+ each(centerNode.inEdges, bind(addAdjacentEdge, null, 'node2'));
+ each(centerNode.outEdges, bind(addAdjacentEdge, null, 'node1'));
+
+ // Sort by radian asc.
+ adjacentEdges.sort((edgeA, edgeB) => edgeA.radToXPosi - edgeB.radToXPosi);
+
+ let availableEdgeCount = 0;
+ const sectionList: SectionWrap[] = [];
+ for (let i = 0, len = adjacentEdges.length; i < len; i++) {
+ const radToXPosiStart = adjacentEdges[i].radToXPosi;
+ const radToXPosiEnd = i < len - 1
+ ? adjacentEdges[i + 1].radToXPosi
+ : adjacentEdges[0].radToXPosi + MATH_2PI;
+
+ // Make sure radToXPosiStart <= radToXPosiEnd.
+ const rad2Minus1 = radToXPosiEnd - radToXPosiStart;
+
+ if (rad2Minus1 >= MIN_EDGE_SECTION_RADIAN) {
+ sectionList.push({ radToXPosiStart, radToXPosiEnd, edgeCount: 0 });
+ }
+ availableEdgeCount += rad2Minus1 / MIN_EDGE_SECTION_RADIAN;
+ }
+
+ if (availableEdgeCount >= selfLoopEdgeCount) {
+ for (let iEdge = 0; iEdge < selfLoopEdgeCount; iEdge++) {
+ // Find the largest section to arrange an edge.
+ let iSecInMax = 0;
+ let secRadInMax = 0;
+ for (let iSec = 0; iSec < sectionList.length; iSec++) {
+ const thisSec = sectionList[iSec];
+ // If a section is too larger than anohter section, split that large section and
+ // arrange multiple edges in it is probably better then arrange only one edge in
+ // the large section.
+ const rad = (thisSec.radToXPosiEnd - thisSec.radToXPosiStart) / (thisSec.edgeCount + 1);
+ if (rad > secRadInMax) {
+ secRadInMax = rad;
+ iSecInMax = iSec;
+ }
+ }
+ sectionList[iSecInMax].edgeCount++;
+ }
+ }
+ // In this case there are probably too many edge on a node, and intersection between
+ // edges can not avoid. So we do not care about intersection any more.
+ else {
+ sectionList.length = 0;
+ sectionList.push({
+ radToXPosiStart: -MATH_PI / 2,
+ radToXPosiEnd: -MATH_PI / 2 + MATH_2PI,
+ edgeCount: selfLoopEdgeCount
+ });
+ }
+
+ return sectionList;
+}
+
+/**
+ * @return cubic bezier curve: [p1, p2, cp1, cp2]
+ */
+function placeSelfLoopEdges(
+ centerNode: GraphNode,
+ sectionList: SectionWrap[],
+ selfLoopEdges: GraphEdge[],
+ nodeScaleOnCoordSys: number
+): void {
+ const symbolSize = getSymbolSize(centerNode);
+ const centerPt = centerNode.getLayout();
+
+ function getCubicControlPoint(radToXPosi: number, cpDistToCenter: number): number[] {
+ return [
+ Math.cos(radToXPosi) * cpDistToCenter + centerPt[0],
+ Math.sin(radToXPosi) * cpDistToCenter + centerPt[1]
+ ];
+ };
+
+ let iEdge = 0;
+ each(sectionList, section => {
+ const secEdgeCount = section.edgeCount;
+ if (!secEdgeCount) {
+ // No self-loop edge arranged in this section.
+ return;
+ }
+
+ const secRadStart = section.radToXPosiStart;
+ const secRadEnd = section.radToXPosiEnd;
+ const splitRadHalfSpan = (secRadEnd - secRadStart) / section.edgeCount / 2;
+ const edgeRadHalfSpan = Math.min(splitRadHalfSpan, MAX_EDGE_SECTION_RADIAN / 2);
+
+ // const radMid = secRadStart + secRadSpan / section.edgeCount * (iEdge - iEdgeFirstInSec);
+ for (let iEdgeInSec = 0; iEdgeInSec < section.edgeCount; iEdgeInSec++) {
+ const edge = selfLoopEdges[iEdge++];
+ const cpMidRad = secRadStart + splitRadHalfSpan * (iEdgeInSec * 2 + 1);
+
+ // This is a experimental strategy to make it look better:
+ // If the symbol size is small, the bezier control point need to be far from the
+ // center to make the buckle obvious, while if the symbol size is big, the control
+ // ponit should not too far to make the buckle too significant.
+ // So we alway make control point dist to symbol radius `100`, and enable users to
+ // use option `curveness` to adjust it.
+ // Becuase at present we do not layout multiple self-loop edges into single
+ // `[cp1Rad, cp2Rad]`, we do not use option `autoCurveness`.
+ const curveness = retrieve2(
+ edge.getModel<GraphEdgeItemOption>().get(['lineStyle', 'curveness']),
+ 0
+ );
+ const cpDistToCenter = (symbolSize / 2 + 100) * nodeScaleOnCoordSys
+ * (curveness + 1)
+ // Formula:
+ // If `cpDistToCenter = symbolSize / 2 * nodeScaleOnCoordSys / 3 * 4 / Math.cos(edgeRadHalfSpan)`,
+ // the control point can be tangent to the symbol circle.
+ // Hint: `distCubicMiddlePtToCenterPt / 3 * 4` get the hight of the isosceles triangle made by
+ // control points and center point.
+ / 3 * 4 / Math.cos(edgeRadHalfSpan);
+
+ edge.setLayout([
+ centerPt.slice(),
+ centerPt.slice(),
+ getCubicControlPoint(cpMidRad - edgeRadHalfSpan, cpDistToCenter),
+ getCubicControlPoint(cpMidRad + edgeRadHalfSpan, cpDistToCenter)
+ ]);
+ }
+ });
+ assert(iEdge === selfLoopEdges.length);
+
+}
+
+/**
+ * @return vector representing the tangant line
+ * (from edge['node1' | 'node2'] to cp1 of the cubic bezier curve)
+ */
+function getTangentVector(edge: GraphEdge, nodeAttr: NodeAttrOnEdge): VectorArray {
+ // points is [p1, p2] or [p1, p2, cp1].
+ const points = edge.getLayout();
+ const targetPt = points[2] ? points[2] : points[1];
+ return sub([], targetPt, edge[nodeAttr].getLayout());
+}
+
+function getRadianToXPositive(vec: VectorArray): Radian {
+ return Math.atan2(vec[1], vec[0]);
+}
+
+export function isSelfLoopEdge(edge: GraphEdge): boolean {
+ return edge.node1 === edge.node2;
+}
diff --git a/src/chart/graph/simpleLayoutHelper.ts b/src/chart/graph/simpleLayoutHelper.ts
index b0c54b3..d65a4a6 100644
--- a/src/chart/graph/simpleLayoutHelper.ts
+++ b/src/chart/graph/simpleLayoutHelper.ts
@@ -19,10 +19,11 @@
import * as vec2 from 'zrender/src/core/vector';
import GraphSeriesModel, { GraphNodeItemOption, GraphEdgeItemOption } from './GraphSeries';
-import Graph, { GraphNode } from '../../data/Graph';
+import Graph from '../../data/Graph';
import * as zrUtil from 'zrender/src/core/util';
-import {getCurvenessForEdge} from '../helper/multipleGraphEdgeHelper';
-import { getNodeGlobalScale } from './graphHelper';
+import { getCurvenessForEdge } from '../helper/multipleGraphEdgeHelper';
+import { isSelfLoopEdge } from './layoutHelper';
+
export function cubicPosition(pt: number[], center: number[], radius: number) {
const rSquare = radius * radius;
@@ -61,6 +62,12 @@ export function simpleLayout(seriesModel: GraphSeriesModel) {
export function simpleLayoutEdge(graph: Graph, seriesModel: GraphSeriesModel) {
graph.eachEdge(function (edge, index) {
+
+ if (isSelfLoopEdge(edge)) {
+ // Self-loop edge will be layout later in `layoutSelfLoopEdges`.
+ return;
+ }
+
const curveness = zrUtil.retrieve3(
edge.getModel<GraphEdgeItemOption>().get(['lineStyle', 'curveness']),
-getCurvenessForEdge(edge, seriesModel, index, true),
@@ -69,80 +76,7 @@ export function simpleLayoutEdge(graph: Graph, seriesModel: GraphSeriesModel) {
const p1 = vec2.clone(edge.node1.getLayout());
const p2 = vec2.clone(edge.node2.getLayout());
const points = [p1, p2];
- if (edge.node1 === edge.node2) {
- const curve = getCurvenessForEdge(edge, seriesModel, index, true);
- const curveness = curve >= 1 ? curve : 1 - curve;
- const symbolSize = seriesModel.get('symbolSize');
- const size = zrUtil.isArray(symbolSize) ? Number((symbolSize[0] + symbolSize[1]) / 2) : Number(symbolSize);
- const radius = getNodeGlobalScale(seriesModel) * size / 2 * curveness;
- const inEdges = edge.node1.inEdges.filter((edge) => {
- return edge.node1 !== edge.node2;
- });
- const outEdges = edge.node1.outEdges.filter((edge) => {
- return edge.node1 !== edge.node2;
- });
- const allNodes: GraphNode[] = [];
- inEdges.forEach((edge) => {
- allNodes.push(edge.node1);
- });
- outEdges.forEach((edge) => {
- allNodes.push(edge.node2);
- });
- const vectors: any[][] = [];
- let d = -Infinity;
- let pt1: number[] = [];
- let pt2: number[] = [];
- if (allNodes.length > 1) {
- allNodes.forEach(node => {
- const v: any[] = [];
- vec2.sub(v, node.getLayout(), edge.node1.getLayout());
- vec2.normalize(v, v);
- vectors.push(v);
- });
- // find the max angle
- for (let i = 0; i < vectors.length; i++) {
- for (let j = i + 1; j < vectors.length; j++) {
- if (vec2.distSquare(vectors[i], vectors[j]) > d) {
- d = vec2.distSquare(vectors[i], vectors[j]);
- pt1 = vectors[i];
- pt2 = vectors[j];
- }
- }
- }
- // if the angle is more than sixty degree
- if (vec2.distSquare(pt1, pt2) > Math.sqrt(3)) {
- vec2.scaleAndAdd(pt1, p1, pt1, radius);
- vec2.scaleAndAdd(pt2, p2, pt2, radius);
- const point1 = cubicPosition(pt1, p1, 10 * radius);
- const point2 = cubicPosition(pt2, p2, 10 * radius);
- const mid = [(point1[0] + point2[0]) / 2, (point1[1] + point2[1]) / 2];
- vec2.sub(mid, mid, p1);
- const degree = Math.atan2(mid[1], mid[0]) / Math.PI * 180;
- const v1 = [Math.cos((degree - 30) * Math.PI / 180), Math.sin((degree - 30) * Math.PI / 180)];
- const v2 = [Math.cos((degree + 30) * Math.PI / 180), Math.sin((degree + 30) * Math.PI / 180)];
- vec2.scaleAndAdd(v1, p1, v1, 10 * radius);
- vec2.scaleAndAdd(v2, p2, v2, 10 * radius);
- points.push(v1, v2);
- }
- else {
- vec2.scaleAndAdd(pt1, p1, pt1, radius);
- vec2.scaleAndAdd(pt2, p2, pt2, radius);
- points.push(cubicPosition(pt1, p1, 10 * radius));
- points.push(cubicPosition(pt2, p2, 10 * radius));
- }
- }
- else {
- points.push([
- p1[0] - radius * 4,
- p2[1] - radius * 6
- ]);
- points.push([
- p1[0] + radius * 4,
- p2[1] - radius * 6
- ]);
- }
- }
- else if (+curveness) {
+ if (+curveness) {
points.push([
(p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness,
(p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness
diff --git a/src/chart/helper/multipleGraphEdgeHelper.ts b/src/chart/helper/multipleGraphEdgeHelper.ts
index f267359..568f252 100644
--- a/src/chart/helper/multipleGraphEdgeHelper.ts
+++ b/src/chart/helper/multipleGraphEdgeHelper.ts
@@ -207,7 +207,8 @@ export function getCurvenessForEdge(edge, seriesModel, index, needReverse?: bool
// if pass array no need parity
const parityCorrection = isArrayParam ? 0 : totalLen % 2 ? 0 : 1;
if (isLoopEdge(edge)) {
- const curveness = curvenessList.filter(num => num > 0);
+ // PENDING: this strategy is not applicable in self-loop edges yet.
+ const curveness = zrUtil.filter(curvenessList, num => num > 0);
return curveness[edgeIndex];
}
else if (!edgeArray.isForward) {
diff --git a/test/graph-self-loop.html b/test/graph-self-loop.html
new file mode 100755
index 0000000..0fd95e1
--- /dev/null
+++ b/test/graph-self-loop.html
@@ -0,0 +1,355 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+
+ <script src="lib/simpleRequire.js"></script>
+ <script src="lib/config.js"></script>
+ <script src="lib/jquery.min.js"></script>
+ <script src="lib/facePrint.js"></script>
+ <script src="lib/testHelper.js"></script>
+ <script src="lib/enableGraphEditRoughly.js"></script>
+ <link rel="stylesheet" href="lib/reset.css" />
+
+ </head>
+ <body>
+ <style>
+ </style>
+
+
+ <div id="main0"></div>
+ <div id="main1"></div>
+
+
+
+
+ <script>
+ function addScaleplate(chart) {
+ if (!chart) {
+ return;
+ }
+ var dom = chart.getDom();
+ var zr = chart.getZr();
+
+ window.__upateScaleplate = upateScaleplate;
+ var els = [];
+
+
+ upateScaleplate(100);
+
+ function upateScaleplate(r) {
+ for (var i = 0; i < els.length; i++) {
+ zr.remove(els[i]);
+ }
+ els.length = 0;
+
+ var cx = 500;
+ var cy = 300;
+ var rCurve = r;
+
+ // var cpx1 = cx + rCurve;
+ // var cpy1 = cy;
+ // var cpx2 = cx;
+ // var cpy2 = cy + rCurve;
+
+ var rCp = rCurve / 3 * 4 / Math.cos(Math.PI / 4);
+ var cpx1 = cx + rCp;
+ var cpy1 = cy;
+ var cpx2 = cx;
+ var cpy2 = cy + rCp;
+
+ els.push(new echarts.graphic.Circle({
+ shape: {cx: cx, cy: cy, r: r},
+ style: {fill: 'rgba(0,0,0,0.2)'}
+ }));
+ els.push(new echarts.graphic.BezierCurve({
+ shape: {
+ x1: cx, y1: cy,
+ x2: cx, y2: cy,
+ cpx1: cpx1, cpy1: cpy1,
+ cpx2: cpx2, cpy2: cpy2
+ },
+ style: {lineWidth: 2, stroke: 'red'}
+ }));
+ els.push(new echarts.graphic.Circle({
+ shape: {cx: cpx1, cy: cpy1, r: 5},
+ style: {fill: 'rgb(10,30,50)'}
+ }));
+ els.push(new echarts.graphic.Circle({
+ shape: {cx: cpx2, cy: cpy2, r: 5},
+ style: {fill: 'rgb(10,30,50)'}
+ }));
+ els.push(new echarts.graphic.Polyline({
+ shape: {points: [[cx, cy], [cpx1, cpy1], [cpx2, cpy2], [cx, cy]]},
+ style: {stroke: 'rgb(10,30,50)'}
+ }));
+ for (var i = 0; i < els.length; i++) {
+ zr.add(els[i]);
+ }
+ }
+ }
+ </script>
+
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+
+ var option = {
+ tooltip: {},
+ series: [{
+ id: 'grh',
+ type: 'graph',
+ symbolSize: 100,
+ roam: 'scale',
+ // draggable : true,1
+ // selectedMode: true,
+ label: {
+ show: true,
+ },
+ edgeSymbol: ['circle', 'arrow'],
+ edgeSymbolSize: [4, 10],
+ edgeLabel: {
+ color: 'green',
+ fontSize: 12,
+ color: 'red',
+ fontSize: 30,
+ },
+ // focusNodeAdjacency: focusNodeAdjacency,
+ lineStyle: {
+ width: 3,
+ color: '#184029',
+ curveness: 0
+ },
+ itemStyle: {
+ opacity: 0.5
+ },
+ emphasis: {
+ focus: 'adjacency'
+ },
+ data: [{
+ name: 'node_1',
+ x: 300,
+ y: 300,
+ value: 'set_style_on_item'
+ }, {
+ name: 'node_2',
+ x: 800,
+ y: 300
+ }, {
+ name: 'node_3',
+ x: 350,
+ y: 100
+ }, {
+ name: 'node_4',
+ x: 550,
+ y: 500
+ }
+ ],
+ links: [{
+ id: 'a',
+ source: 0,
+ target: 1,
+ symbolSize: [5, 20],
+ lineStyle: {
+ width: 5,
+ opacity: 1,
+ curveness: 0.2
+ },
+ emphasis: {
+ lineStyle: {
+ color: 'blue',
+ width: 20,
+ opacity: 0.1
+ }
+ }
+ }, {
+ id: 'b',
+ source: 'node_2',
+ target: 'node_1',
+ label: {
+ show: true
+ },
+ lineStyle: {
+ curveness: 0.2
+ }
+ }, {
+ id: 'c',
+ source: 'node_2',
+ target: 'node_1',
+ lineStyle: {
+ curveness: 0.8
+ }
+ }, {
+ id: 'd',
+ source: 'node_1',
+ target: 'node_3',
+ emphasis: {
+ label: {
+ show: true
+ }
+ }
+ }, {
+ id: 'e',
+ source: 'node_1',
+ target: 'node_4'
+ },
+
+ {
+ id: 'f',
+ source: 'node_1',
+ target: 'node_1',
+ },
+ {
+ id: 'g',
+ source: 'node_1',
+ target: 'node_1',
+ lineStyle: {
+ // curveness: 0.9
+ },
+ label: {
+ show: true
+ }
+ },
+
+ ],
+ autoCurveness: true
+ }
+ ]};
+
+
+ var chart = testHelper.create(echarts, 'main0', {
+ option: option,
+ height: 600,
+ title: [
+ 'Drag a node to modify the angles',
+ 'Drag an edge to modify the curveness',
+ 'Wheel to scale',
+ ]
+ });
+
+ if (chart) {
+ enableGraphEditRoughly({
+ chart: chart,
+ option: option,
+ seriesId: 'grh',
+ drag: true,
+ editNodeSize: true,
+ editSelfLoopEdgeCount: true,
+ selfLoopEdgeNodeName: 'node_1'
+ });
+ // addScaleplate(chart);
+ }
+
+ });
+ </script>
+
+
+
+
+
+
+
+
+
+
+ <script>
+ require(['echarts'], function (echarts) {
+ var option = {
+ tooltip: {},
+ animationDurationUpdate: 1500,
+ animationEasingUpdate: 'quinticInOut',
+ series : [
+ {
+ type: 'graph',
+ id: 'grh',
+ symbolSize: 120,
+ roam: 'scale',
+ // draggable : true,1
+ // selectedMode: true,
+ label: {
+ normal: {
+ show: true
+ }
+ },
+ edgeSymbol: ['circle', 'arrow'],
+ edgeSymbolSize: [4, 10],
+ // focusNodeAdjacency: focusNodeAdjacency,
+ lineStyle: {
+ width: 3,
+ color: '#184029',
+ curveness: 0
+ },
+ itemStyle: {
+ opacity: 0.5
+ },
+ data: [{
+ name: 'node_1',
+ x: 300,
+ y: 300,
+ value: 'set_style_on_item'
+ }, {
+ name: 'node_2',
+ x: 800,
+ y: 300
+ }
+ ],
+ links: [{
+ source: 'node_2',
+ target: 'node_1',
+ label: {
+ show: true
+ },
+ lineStyle: {
+ curveness: 0.2
+ }
+ },
+ {
+ source: 'node_1',
+ target: 'node_1',
+ },
+ {
+ source: 'node_1',
+ target: 'node_1',
+ lineStyle: {
+ curveness: 0.9
+ },
+ label: {
+ show: true
+ }
+ },
+
+ ],
+ autoCurveness: true
+ }
+ ]
+ };
+ var chart = testHelper.create(echarts, 'main1', {
+ option: option,
+ height: 500,
+ title: [
+ 'Drag a node to modify the angles',
+ 'Drag an edge to modify the curveness',
+ 'Wheel to scale',
+ ]
+ });
+
+ if (chart) {
+ enableGraphEditRoughly({
+ chart: chart,
+ option: option,
+ seriesId: 'grh',
+ drag: true,
+ editNodeSize: true,
+ editSelfLoopEdgeCount: true,
+ selfLoopEdgeNodeName: 'node_1'
+ });
+ }
+ });
+ </script>
+
+
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/test/lib/enableGraphEditRoughly.js b/test/lib/enableGraphEditRoughly.js
new file mode 100644
index 0000000..80b53e1
--- /dev/null
+++ b/test/lib/enableGraphEditRoughly.js
@@ -0,0 +1,512 @@
+(function () {
+
+ var NODE_SIZE_MIN_DEFAULT = 0;
+ var NODE_SIZE_MAX_DEFAULT = 300;
+ var SELF_LOOP_EDGE_COUNT_MAX = 10;
+
+ /**
+ * @param opt
+ * @param opt.chart
+ * @param opt.option
+ * @param opt.seriesId
+ *
+ * @param opt.drag {boolean} Enable drag nodes and edges.
+ *
+ * @param opt.editNodeSize {boolean}
+ * @param opt.nodeSizeMin `NODE_SIZE_MIN_DEFAULT` by default.
+ * @param opt.nodeSizeMax `NODE_SIZE_MAX_DEFAULT` by default.
+ *
+ * @param opt.editSelfLoopEdgeCount {boolean}
+ * @param opt.selfLoopEdgeCountMax `SELF_LOOP_EDGE_COUNT_MAX` by default.
+ * @param opt.selfLoopEdgeNodeName {string}
+ */
+ window.enableGraphEditRoughly = function (opt) {
+ if (!opt.chart) {
+ return;
+ }
+
+ if (opt.drag) {
+ enableGraphDrag(opt);
+ }
+ if (opt.editNodeSize) {
+ enableEditNodeSize(opt);
+ }
+ if (opt.editSelfLoopEdgeCount) {
+ enableEditSelfLoopEdgeCount(opt);
+ }
+ };
+
+ /**
+ * @param opt
+ * @param opt.chart
+ * @param opt.option
+ * @param opt.seriesId
+ */
+ function enableGraphDrag(opt) {
+ opt = opt || {};
+ var chart = opt.chart;
+ var option = opt.option;
+ var seriesId = opt.seriesId;
+
+ assert(chart && option && seriesId);
+
+ var zr = chart.getZr();
+
+ /**
+ * type Dragging = {
+ * type: 'node',
+ * dataIndex: number,
+ * mouseDownPoint: [number, number],
+ * } | {
+ * type: 'edge',
+ * edgeDataIndex: number,
+ * mouseDownPoint: [number, number],
+ * curveness: number
+ * }
+ */
+ var dragging = null;
+
+ var seriesNodesOption = findSeriesNodesOption(option, seriesId);
+ var seriesEdgesOption = findSeriesEdgesOption(option, seriesId);
+
+ var seriesModel = findSeriesModel(chart, seriesId);
+ var seriesData = seriesModel.getData();
+ var seriesEdgeData = seriesModel.getData('edge');
+
+ zr.on('mousedown', function (event) {
+ mouseDownPoint = [event.offsetX, event.offsetY];
+ var nodeResult = findSeriesDataItemByEvent(seriesData, event);
+ if (nodeResult) {
+ dragging = {
+ type: 'node',
+ dataIndex: nodeResult.dataIndex,
+ mouseDownPoint: mouseDownPoint
+ };
+ return;
+ }
+
+ var edgeResult = findSeriesDataItemByEvent(seriesEdgeData, event);
+ if (edgeResult) {
+ dragging = {
+ type: 'edge',
+ edgeDataIndex: edgeResult.dataIndex,
+ mouseDownPoint: mouseDownPoint,
+ curveness: getCurrentCurveness(seriesEdgesOption, edgeResult.dataIndex)
+ };
+ return;
+ }
+ });
+ zr.on('mousemove', function (event) {
+ if (!dragging) {
+ return;
+ }
+
+ if (dragging.type === 'node') {
+ var dataItemOption = seriesNodesOption[dragging.dataIndex];
+ var nextDataXY = chart.convertFromPixel(
+ {seriesId: seriesId},
+ [event.offsetX, event.offsetY]
+ );
+ dataItemOption.x = nextDataXY[0];
+ dataItemOption.y = nextDataXY[1];
+ chart.setOption({
+ animation: false,
+ series: {
+ id: seriesId,
+ data: seriesNodesOption
+ }
+ });
+ }
+ else if (dragging.type === 'edge') {
+ var nextCurveness = getNextCurveness(
+ chart,
+ seriesId,
+ dragging.curveness,
+ [event.offsetX, event.offsetY],
+ seriesEdgeData,
+ dragging.edgeDataIndex
+ );
+ updateCurvenessOption(seriesEdgesOption, dragging.edgeDataIndex, nextCurveness);
+
+ chart.setOption({
+ animation: false,
+ series: {
+ id: seriesId,
+ edges: seriesEdgesOption
+ }
+ });
+ }
+ });
+ zr.on('mouseup', function (event) {
+ dragging = null;
+ });
+ };
+
+ function getNextCurveness(
+ chart, seriesId, mouseDownCurveness, mouseMovePoint,
+ seriesEdgeData, edgeDataIndex
+ ) {
+ var edgePoints = getEdgePoints(chart, seriesId, seriesEdgeData, edgeDataIndex)
+
+ var vv = makeVector(edgePoints.from, edgePoints.to);
+ var vSqrDist = vectorSquareDist(vv);
+ var sign = 1;
+ if (!aroundZero(vSqrDist)) {
+ var detResult = det(vv, makeVector(mouseMovePoint, edgePoints.from));
+ sign = detResult > 0 ? 1 : -1;
+ }
+
+ mouseDownCurveness = mouseDownCurveness || 0;
+ var dist = distPointToLine(mouseMovePoint, edgePoints.from, edgePoints.to);
+ var curveDist = dist / 300;
+ return sign * curveDist;
+ }
+
+ /**
+ * @param opt
+ * @param opt.chart
+ * @param opt.seriesId
+ * @param opt.nodeSizeMin
+ * @param opt.nodeSizeMax
+ */
+ function enableEditNodeSize(opt) {
+ opt = opt || {};
+ var chart = opt.chart;
+ var seriesId = opt.seriesId;
+
+ assert(chart && seriesId);
+
+ prepareControlPanel(chart);
+
+ var nodeSizeMin = opt.nodeSizeMin || NODE_SIZE_MIN_DEFAULT;
+ var nodeSizeMax = opt.nodeSizeMax || NODE_SIZE_MAX_DEFAULT;
+
+ addSlider(
+ chart.__controlPanelEl,
+ 'symbol size:',
+ nodeSizeMin,
+ nodeSizeMax,
+ 1,
+ function (newValue) {
+ console.log('symbolSize:', newValue);
+ chart.setOption({
+ animation: false,
+ series: {
+ id: seriesId,
+ symbolSize: +newValue
+ }
+ });
+ }
+ );
+ };
+
+ /**
+ * @param opt
+ * @param opt.chart
+ * @param opt.seriesId
+ * @param opt.option
+ * @param opt.selfLoopEdgeCountMax
+ * @param opt.selfLoopEdgeNodeName
+ */
+ function enableEditSelfLoopEdgeCount(opt) {
+ opt = opt || {};
+ var chart = opt.chart;
+ var option = opt.option;
+ var seriesId = opt.seriesId;
+ var selfLoopEdgeNodeName = opt.selfLoopEdgeNodeName;
+ var selfLoopEdgeCountMax = opt.selfLoopEdgeCountMax || SELF_LOOP_EDGE_COUNT_MAX;
+
+ assert(chart && seriesId && option);
+
+ prepareControlPanel(chart);
+
+ addSlider(
+ chart.__controlPanelEl,
+ 'self-loop edge count:',
+ 0,
+ selfLoopEdgeCountMax,
+ 1,
+ function (newValue) {
+ console.log('self_loop_edge_count:', newValue);
+
+ var seriesEdgesOption = findSeriesEdgesOption(option, seriesId);
+ var seriesModel = findSeriesModel(chart, seriesId);
+ var seriesData = seriesModel.getData();
+
+ var edgeCount = 0;
+ for (var i = 0; i < seriesEdgesOption.length;) {
+ var seriesEdgeItemOption = seriesEdgesOption[i];
+ var sourceName = findNodeNameByNameOrIndex(seriesData, seriesEdgeItemOption.source);
+ var targetName = findNodeNameByNameOrIndex(seriesData, seriesEdgeItemOption.target);
+
+ if (sourceName && sourceName === targetName && sourceName === selfLoopEdgeNodeName) {
+ edgeCount++;
+ }
+ if (edgeCount > newValue) {
+ seriesEdgesOption.splice(i, 1);
+ }
+ else {
+ i++;
+ }
+ }
+ for (var i = edgeCount; i < newValue; i++) {
+ seriesEdgesOption.push({
+ source: selfLoopEdgeNodeName,
+ target: selfLoopEdgeNodeName
+ });
+ }
+
+ chart.setOption({
+ animation: false,
+ series: {
+ id: seriesId,
+ edges: seriesEdgesOption,
+ links: null
+ }
+ });
+ }
+ );
+ }
+
+
+ // ----------------------------------
+ // Utils
+ // ----------------------------------
+
+
+ function prepareControlPanel(chart) {
+ if (chart.__controlPanelEl) {
+ return;
+ }
+
+ var el = document.createElement('div');
+ el.style.cssText = [
+ 'position: absolute',
+ 'top: 0',
+ 'right: 0',
+ 'padding: 15px;',
+ 'box-shadow: 0 2px 5px #000',
+ ].join(';');
+
+ chart.getDom().appendChild(el);
+ chart.__controlPanelEl = el;
+ }
+
+ function addSlider(controlPanelEl, labelHTML, min, max, step, onInput) {
+ var lineEl = document.createElement('div');
+ lineEl.style.cssText = [
+ 'text-align: right'
+ ].join('');
+ controlPanelEl.appendChild(lineEl);
+
+ var label = document.createElement('span');
+ label.innerHTML = labelHTML;
+ label.style.cssText = [
+ 'vertical-align: middle',
+ 'padding-right: 10px;'
+ ].join(';');
+ lineEl.appendChild(label);
+
+ var slider = document.createElement('input');
+ slider.style.cssText = [
+ 'vertical-align: middle'
+ ].join('');
+ slider.setAttribute('type', 'range');
+ slider.setAttribute('min', min);
+ slider.setAttribute('max', max);
+ slider.setAttribute('step', step);
+ slider.oninput = function () {
+ valueEl.innerHTML = this.value;
+ onInput(this.value);
+ };
+ lineEl.appendChild(slider);
+
+ var valueEl = document.createElement('span');
+ valueEl.style.cssText = [
+ 'display: inline-block',
+ 'vertical-align: middle',
+ 'padding-left: 10px;',
+ 'min-width: 30px'
+ ].join(';');
+ lineEl.appendChild(valueEl);
+ }
+
+ function findEdgeItemOption(seriesEdgesOption, edgeDataIndex) {
+ var edgeItemOption = seriesEdgesOption[edgeDataIndex];
+ return edgeItemOption.lineStyle || (edgeItemOption.lineStyle = {});
+ }
+
+ function getCurrentCurveness(seriesEdgesOption, edgeDataIndex) {
+ var lineStyleOption = findEdgeItemOption(seriesEdgesOption, edgeDataIndex);
+ return (lineStyleOption || lineStyleOption.normal || {}).curveness || 0;
+ }
+
+ function updateCurvenessOption(seriesEdgesOption, edgeDataIndex, curveness) {
+ // format legacy option: `lineStyle.normal.curveness`
+ for (var i = 0; i < seriesEdgesOption.length; i++) {
+ var edgeItemOption = seriesEdgesOption[i];
+ var lineStyleOption = edgeItemOption.lineStyle;
+ if (lineStyleOption && lineStyleOption.normal) {
+ extend(lineStyleOption, lineStyleOption.normal);
+ }
+ }
+
+ var lineStyleOption = findEdgeItemOption(seriesEdgesOption, edgeDataIndex);
+ lineStyleOption.curveness = curveness || 0;
+
+ console.log('edgeDataIndex: ', edgeDataIndex, 'curveness: ', lineStyleOption.curveness);
+ }
+
+ function getEdgePoints(chart, seriesId, seriesEdgeData, edgeDataIndex) {
+ var edgeLayout = seriesEdgeData.getItemLayout(edgeDataIndex);
+ assert(edgeLayout && edgeLayout.__original);
+ var originalPoints = edgeLayout.__original;
+ assert(originalPoints[0] && originalPoints[1]);
+ return {
+ from: chart.convertToPixel({seriesId: seriesId}, originalPoints[0]),
+ to: chart.convertToPixel({seriesId: seriesId}, originalPoints[1])
+ };
+ }
+
+ function findSeriesNodesOption(option, seriesId) {
+ var seriesOption = findSeriesOption(option, seriesId);
+ var seriesNodesOption = seriesOption.data || seriesOption.nodes;
+ assert(isArray(seriesNodesOption));
+ return seriesNodesOption;
+ }
+
+ function findSeriesEdgesOption(option, seriesId) {
+ var seriesOption = findSeriesOption(option, seriesId);
+ var seriesEdgesOption = seriesOption.edges || seriesOption.links;
+ assert(isArray(seriesEdgesOption));
+ return seriesEdgesOption;
+ }
+
+ function findSeriesOption(option, seriesId) {
+ var seriesOption = (
+ isArray(option.series) ? option.series : [option.series]
+ ).filter(function (seriesOpt) {
+ return seriesOpt.id === seriesId;
+ })[0];
+ assert(seriesOption);
+ return seriesOption;
+ }
+
+ function findSeriesModel(chart, seriesId) {
+ var seriesModel = chart.getModel().getSeries().filter(function (series) {
+ return series.id === seriesId
+ })[0];
+ assert(seriesModel);
+ return seriesModel;
+ }
+
+ function findNodeNameByNameOrIndex(seriesEdgeData, nameOfIndex) {
+ if (isNumber(nameOfIndex)) {
+ return seriesEdgeData.getName(nameOfIndex);
+ }
+ else {
+ return nameOfIndex;
+ }
+ }
+
+ // The input el will also be called to cb.
+ function travelAncestor(el, cb) {
+ var currEl = el
+ while (currEl) {
+ var stop = cb(currEl);
+ if (stop) {
+ break;
+ }
+ currEl = currEl.parent ? currEl.parent
+ // text el attached to some element
+ : currEl.__hostTarget ? currEl.__hostTarget
+ : null;
+ }
+ }
+
+ function findSeriesDataItemByEvent(seriesData, event) {
+ var eventTarget = event.target;
+ if (!eventTarget) {
+ return;
+ }
+
+ for (var i = 0, len = seriesData.count(); i < len; i++) {
+ var itemEl = seriesData.getItemGraphicEl(i);
+
+ var isThisItem;
+ travelAncestor(eventTarget, function (ancestorEl) {
+ if (ancestorEl === itemEl) {
+ isThisItem = true;
+ return true;
+ }
+ });
+
+ if (isThisItem) {
+ return {
+ dataIndex: i,
+ el: itemEl
+ };
+ }
+ }
+ }
+
+ function assert(condition, message) {
+ if (!condition) {
+ throw new Error(message);
+ }
+ }
+
+ function isArray(some) {
+ return Object.prototype.toString.call(some) === '[object Array]';
+ }
+
+ function makeVector(from, to) {
+ return [to[0] - from[0], to[1] - from[1]];
+ }
+
+ function det(v0, v1) {
+ return v0[0] * v1[1] - v0[1] * v1[0];
+ }
+
+ function dot(v0, v1) {
+ return v0[0] * v1[0] + v0[1] * v1[1];
+ }
+
+ function vectorSquareDist(vv) {
+ return vv[0] * vv[0] + vv[1] * vv[1];
+ }
+
+ function distPointToLine(point, v0, v1) {
+ var pv = makeVector(v0, point);
+ var vv = makeVector(v0, v1);
+ var vSqrDist = vectorSquareDist(vv);
+ if (aroundZero(vSqrDist)) {
+ return vectorSquareDist(pv);
+ }
+
+ var tt = dot(pv, vv) / vSqrDist;
+ tt = Math.max(0, Math.min(1, tt));
+ var crossPoint = [v0[0] + tt * vv[0], v0[1] + tt * vv[1]];
+ var sqrDist = vectorSquareDist(makeVector(point, crossPoint));
+
+ return Math.sqrt(sqrDist);
+ }
+
+ function aroundZero(v) {
+ return Math.abs(v) < 1e-4;
+ }
+
+ function isNumber(v) {
+ return Object.prototype.toString.call(v) === '[object Number]';
+ }
+
+ function extend(target, source) {
+ for (var key in source) {
+ if (source.hasOwnProperty(key)) {
+ target[key] = source[key];
+ }
+ }
+ return target;
+ }
+
+})();
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org