diff --git a/src/chart/graph/GraphSeries.js b/src/chart/graph/GraphSeries.js index 13682126d3e0d6737c57340b3de7862eb9666902..e38e71a1f3c98756e6dad0e236cb76eaf007e187 100644 --- a/src/chart/graph/GraphSeries.js +++ b/src/chart/graph/GraphSeries.js @@ -25,6 +25,8 @@ import Model from '../../model/Model'; import {encodeHTML} from '../../util/format'; import createGraphFromNodeEdge from '../helper/createGraphFromNodeEdge'; import LegendVisualProvider from '../../visual/LegendVisualProvider'; +import {initCurvenessList} from '../helper/multipleGraphEdgeHelper'; +import {createEdgeMapForCurveness} from '../../chart/helper/multipleGraphEdgeHelper'; var GraphSeries = echarts.extendSeriesModel({ @@ -66,7 +68,13 @@ var GraphSeries = echarts.extendSeriesModel({ var self = this; if (nodes && edges) { - return createGraphFromNodeEdge(nodes, edges, this, true, beforeLink).data; + // auto curveness + initCurvenessList(this); + var graph = createGraphFromNodeEdge(nodes, edges, this, true, beforeLink); + zrUtil.each(graph.edges, function (edge) { + createEdgeMapForCurveness(edge.node1, edge.node2, this, edge.dataIndex); + }, this); + return graph.data; } function beforeLink(nodeData, edgeData) { @@ -278,7 +286,6 @@ var GraphSeries = echarts.extendSeriesModel({ lineStyle: { color: '#aaa', width: 1, - curveness: 0, opacity: 0.5 }, emphasis: { @@ -289,4 +296,4 @@ var GraphSeries = echarts.extendSeriesModel({ } }); -export default GraphSeries; \ No newline at end of file +export default GraphSeries; diff --git a/src/chart/graph/circularLayoutHelper.js b/src/chart/graph/circularLayoutHelper.js index 3552c626dffee38a71c73b939aed1d616810045f..fd9e6b7f8fbebd924515b84f541fee0759c7bbf4 100644 --- a/src/chart/graph/circularLayoutHelper.js +++ b/src/chart/graph/circularLayoutHelper.js @@ -19,6 +19,8 @@ import * as vec2 from 'zrender/src/core/vector'; import {getSymbolSize, getNodeGlobalScale} from './graphHelper'; +import * as zrUtil from 'zrender/src/core/util'; +import {getCurvenessForEdge} from '../helper/multipleGraphEdgeHelper'; var PI = Math.PI; @@ -73,8 +75,12 @@ export function circularLayout(seriesModel, basedOn) { _layoutNodesBasedOn[basedOn](seriesModel, coordSys, graph, nodeData, r, cx, cy, count); - graph.eachEdge(function (edge) { - var curveness = edge.getModel().get('lineStyle.curveness') || 0; + graph.eachEdge(function (edge, index) { + var curveness = zrUtil.retrieve3( + edge.getModel().get('lineStyle.curveness'), + getCurvenessForEdge(edge, seriesModel, index), + 0 + ); var p1 = vec2.clone(edge.node1.getLayout()); var p2 = vec2.clone(edge.node2.getLayout()); var cp1; diff --git a/src/chart/graph/forceLayout.js b/src/chart/graph/forceLayout.js index e61555ac1d73d1a26d0e11c97d0ba18f3afff227..a305757e974a5592c0ba69555caa76e880896731 100644 --- a/src/chart/graph/forceLayout.js +++ b/src/chart/graph/forceLayout.js @@ -23,6 +23,7 @@ import {circularLayout} from './circularLayoutHelper'; import {linearMap} from '../../util/number'; import * as vec2 from 'zrender/src/core/vector'; import * as zrUtil from 'zrender/src/core/util'; +import {getCurvenessForEdge} from '../helper/multipleGraphEdgeHelper'; export default function (ecModel) { ecModel.eachSeriesByType('graph', function (graphSeries) { @@ -84,11 +85,16 @@ export default function (ecModel) { d = (edgeLength[0] + edgeLength[1]) / 2; } var edgeModel = edge.getModel(); + var curveness = zrUtil.retrieve3( + edge.getModel().get('lineStyle.curveness'), + -getCurvenessForEdge(edge, graphSeries, idx, true), + 0 + ); return { n1: nodes[edge.node1.dataIndex], n2: nodes[edge.node2.dataIndex], d: d, - curveness: edgeModel.get('lineStyle.curveness') || 0, + curveness: curveness, ignoreForceLayout: edgeModel.get('ignoreForceLayout') }; }); diff --git a/src/chart/graph/simpleLayoutHelper.js b/src/chart/graph/simpleLayoutHelper.js index 83d026c617d81a3c308575db5aaa78dcce77c707..f25fc0d0dc43a9b2014438316ed782121a8e00c2 100644 --- a/src/chart/graph/simpleLayoutHelper.js +++ b/src/chart/graph/simpleLayoutHelper.js @@ -18,6 +18,9 @@ */ import * as vec2 from 'zrender/src/core/vector'; +import * as zrUtil from 'zrender/src/core/util'; +import {getCurvenessForEdge} from '../helper/multipleGraphEdgeHelper'; + export function simpleLayout(seriesModel) { var coordSys = seriesModel.coordinateSystem; @@ -31,12 +34,16 @@ export function simpleLayout(seriesModel) { node.setLayout([+model.get('x'), +model.get('y')]); }); - simpleLayoutEdge(graph); + simpleLayoutEdge(graph, seriesModel); } -export function simpleLayoutEdge(graph) { - graph.eachEdge(function (edge) { - var curveness = edge.getModel().get('lineStyle.curveness') || 0; +export function simpleLayoutEdge(graph, seriesModel) { + graph.eachEdge(function (edge, index) { + var curveness = zrUtil.retrieve3( + edge.getModel().get('lineStyle.curveness'), + -getCurvenessForEdge(edge, seriesModel, index, true), + 0 + ); var p1 = vec2.clone(edge.node1.getLayout()); var p2 = vec2.clone(edge.node2.getLayout()); var points = [p1, p2]; @@ -48,4 +55,4 @@ export function simpleLayoutEdge(graph) { } edge.setLayout(points); }); -} \ No newline at end of file +} diff --git a/src/chart/helper/createGraphFromNodeEdge.js b/src/chart/helper/createGraphFromNodeEdge.js index e78087e8e6b1801a0e2bae5377ffc441eb882fc4..8f875f35253114283c2f14b24924be5da6509c53 100644 --- a/src/chart/helper/createGraphFromNodeEdge.js +++ b/src/chart/helper/createGraphFromNodeEdge.js @@ -39,12 +39,14 @@ export default function (nodes, edges, seriesModel, directed, beforeLink) { var linkNameList = []; var validEdges = []; var linkCount = 0; + for (var i = 0; i < edges.length; i++) { var link = edges[i]; var source = link.source; var target = link.target; + // addEdge may fail when source or target not exists - if (graph.addEdge(source, target, linkCount)) { + if (graph.addEdge(source, target, linkCount, seriesModel)) { validEdges.push(link); linkNameList.push(zrUtil.retrieve(link.id, source + ' > ' + target)); linkCount++; @@ -89,6 +91,5 @@ export default function (nodes, edges, seriesModel, directed, beforeLink) { // Update dataIndex of nodes and edges because invalid edge may be removed graph.update(); - return graph; } diff --git a/src/chart/helper/multipleGraphEdgeHelper.js b/src/chart/helper/multipleGraphEdgeHelper.js new file mode 100644 index 0000000000000000000000000000000000000000..cdc0b3447665e00d50f24b5c06b156dbc7b2432f --- /dev/null +++ b/src/chart/helper/multipleGraphEdgeHelper.js @@ -0,0 +1,229 @@ +/* +* 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'; + +var KEY_DELIMITER = '-->'; +/** + * params handler + * @param {module:echarts/model/SeriesModel} seriesModel + * @returns {*} + */ +var getAutoCurvenessParams = function (seriesModel) { + return seriesModel.get('autoCurveness') || null; +}; + +/** + * Generate a list of edge curvatures, 20 is the default + * @param {module:echarts/model/SeriesModel} seriesModel + * @param {number} appendLength + * @return 20 => [0, -0.2, 0.2, -0.4, 0.4, -0.6, 0.6, -0.8, 0.8, -1, 1, -1.2, 1.2, -1.4, 1.4, -1.6, 1.6, -1.8, 1.8, -2] + */ +var createCurveness = function (seriesModel, appendLength) { + var autoCurvenessParmas = getAutoCurvenessParams(seriesModel); + var length = 20; + var curvenessList = []; + + // handler the function set + if (typeof autoCurvenessParmas === 'number') { + length = autoCurvenessParmas; + } + else if (zrUtil.isArray(autoCurvenessParmas)) { + seriesModel.__curvenessList = autoCurvenessParmas; + return; + } + + // append length + if (appendLength > length) { + length = appendLength; + } + + // make sure the length is even + var len = length % 2 ? length + 2 : length + 3; + curvenessList = []; + + for (var i = 0; i < len; i++) { + curvenessList.push((i % 2 ? i + 1 : i) / 10 * (i % 2 ? -1 : 1)); + } + seriesModel.__curvenessList = curvenessList; +}; + +/** + * Create different cache key data in the positive and negative directions, in order to set the curvature later + * @param {number|string|module:echarts/data/Graph.Node} n1 + * @param {number|string|module:echarts/data/Graph.Node} n2 + * @param {module:echarts/model/SeriesModel} seriesModel + * @returns {string} key + */ +var getKeyOfEdges = function (n1, n2, seriesModel) { + var source = [n1.id, n1.dataIndex].join('.'); + var target = [n2.id, n2.dataIndex].join('.'); + return [seriesModel.uid, source, target].join(KEY_DELIMITER); +}; + +/** + * get opposite key + * @param {string} key + * @returns {string} + */ +var getOppositeKey = function (key) { + var keys = key.split(KEY_DELIMITER); + return [keys[0], keys[2], keys[1]].join(KEY_DELIMITER); +}; + +/** + * get edgeMap with key + * @param edge + * @param {module:echarts/model/SeriesModel} seriesModel + */ +var getEdgeFromMap = function (edge, seriesModel) { + var key = getKeyOfEdges(edge.node1, edge.node2, seriesModel); + return seriesModel.__edgeMap[key]; +}; + +/** + * calculate all cases total length + * @param edge + * @param seriesModel + * @returns {number} + */ +var getTotalLengthBetweenNodes = function (edge, seriesModel) { + var len = getEdgeMapLengthWithKey(getKeyOfEdges(edge.node1, edge.node2, seriesModel), seriesModel); + var lenV = getEdgeMapLengthWithKey(getKeyOfEdges(edge.node2, edge.node1, seriesModel), seriesModel); + + return len + lenV; +}; + +/** + * + * @param key + */ +var getEdgeMapLengthWithKey = function (key, seriesModel) { + var edgeMap = seriesModel.__edgeMap; + return edgeMap[key] ? edgeMap[key].length : 0; +}; + +/** + * Count the number of edges between the same two points, used to obtain the curvature table and the parity of the edge + * @see /graph/GraphSeries.js@getInitialData + * @param {module:echarts/model/SeriesModel} seriesModel + */ +export function initCurvenessList(seriesModel) { + if (!getAutoCurvenessParams(seriesModel)) { + return; + } + + seriesModel.__curvenessList = []; + seriesModel.__edgeMap = {}; + // calc the array of curveness List + createCurveness(seriesModel); +} + +/** + * set edgeMap with key + * @param {number|string|module:echarts/data/Graph.Node} n1 + * @param {number|string|module:echarts/data/Graph.Node} n2 + * @param {module:echarts/model/SeriesModel} seriesModel + * @param {number} index + */ +export function createEdgeMapForCurveness(n1, n2, seriesModel, index) { + if (!getAutoCurvenessParams(seriesModel)) { + return; + } + + var key = getKeyOfEdges(n1, n2, seriesModel); + var edgeMap = seriesModel.__edgeMap; + var oppositeEdges = edgeMap[getOppositeKey(key)]; + // set direction + if (edgeMap[key] && !oppositeEdges) { + edgeMap[key].isForward = true; + } + else if (oppositeEdges && edgeMap[key]) { + oppositeEdges.isForward = true; + edgeMap[key].isForward = false; + } + + edgeMap[key] = edgeMap[key] || []; + edgeMap[key].push(index); +} + +/** + * get curvature for edge + * @param edge + * @param {module:echarts/model/SeriesModel} seriesModel + * @param index + */ +export function getCurvenessForEdge(edge, seriesModel, index, needReverse) { + const autoCurvenessParams = getAutoCurvenessParams(seriesModel); + const isArrayParam = zrUtil.isArray(autoCurvenessParams); + if (!autoCurvenessParams) { + return null; + } + + var edgeArray = getEdgeFromMap(edge, seriesModel); + if (!edgeArray) { + return null; + } + + var edgeIndex = -1; + for (var i = 0; i < edgeArray.length; i++) { + if (edgeArray[i] === index) { + edgeIndex = i; + break; + } + } + // if totalLen is Longer createCurveness + var totalLen = getTotalLengthBetweenNodes(edge, seriesModel); + createCurveness(seriesModel, totalLen); + + edge.lineStyle = edge.lineStyle || {}; + // if is opposite edge, must set curvenss to opposite number + var curKey = getKeyOfEdges(edge.node1, edge.node2, seriesModel); + var curvenessList = seriesModel.__curvenessList; + // if pass array no need parity + var parityCorrection = isArrayParam ? 0 : totalLen % 2 ? 0 : 1; + + if (!edgeArray.isForward) { + // the opposite edge show outside + var oppositeKey = getOppositeKey(curKey); + var len = getEdgeMapLengthWithKey(oppositeKey, seriesModel); + var resValue = curvenessList[edgeIndex + len + parityCorrection]; + // isNeedReverse, simple, force type need reverse the curveness in the junction of the forword and the opposite + if (needReverse) { + // set as array may make the parity handle with the len of opposite + if (isArrayParam) { + if (autoCurvenessParams && autoCurvenessParams[0] === 0) { + return (len + parityCorrection) % 2 ? resValue : -resValue; + } + else { + return ((len % 2 ? 0 : 1) + parityCorrection) % 2 ? resValue : -resValue; + } + } + else { + return (len + parityCorrection) % 2 ? resValue : -resValue; + } + } + else { + return curvenessList[edgeIndex + len + parityCorrection]; + } + } + else { + return curvenessList[parityCorrection + edgeIndex]; + } +} diff --git a/src/data/Graph.js b/src/data/Graph.js index 17b10b75b4eb71539a08b73122b2fd2ddeabe4b8..6c0fca8e5e3886734bec63d1a3077fb5b1ac15db 100644 --- a/src/data/Graph.js +++ b/src/data/Graph.js @@ -137,9 +137,10 @@ graphProto.getNodeById = function (id) { * @param {number|string|module:echarts/data/Graph.Node} n1 * @param {number|string|module:echarts/data/Graph.Node} n2 * @param {number} [dataIndex=-1] + * @param {module:echarts/model/SeriesModel} seriesModel * @return {module:echarts/data/Graph.Edge} */ -graphProto.addEdge = function (n1, n2, dataIndex) { +graphProto.addEdge = function (n1, n2, dataIndex, seriesModel) { var nodesMap = this._nodesMap; var edgesMap = this._edgesMap; @@ -162,11 +163,6 @@ graphProto.addEdge = function (n1, n2, dataIndex) { } var key = n1.id + '-' + n2.id; - // PENDING - if (edgesMap[key]) { - return; - } - var edge = new Edge(n1, n2, dataIndex); edge.hostGraph = this; diff --git a/test/graph-mulitple-edges.html b/test/graph-mulitple-edges.html new file mode 100644 index 0000000000000000000000000000000000000000..9b3d6a36e37a200cbcf84791f0c433ed87b111c7 --- /dev/null +++ b/test/graph-mulitple-edges.html @@ -0,0 +1,357 @@ + + + + + + + + + + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + + + + + + + + + +