From 6670af76cc73a6f62aba28e79fb3d93c367e3b1b Mon Sep 17 00:00:00 2001 From: lang Date: Thu, 10 Dec 2015 13:04:59 +0800 Subject: [PATCH] Symbol draggable, force enhancement --- src/chart/graph/GraphSeries.js | 3 +- src/chart/graph/GraphView.js | 25 ++++++- src/chart/graph/forceHelper.js | 51 ++++++++----- src/chart/graph/forceLayout.js | 15 +++- src/chart/helper/Symbol.js | 12 +++ src/chart/helper/createGraphFromNodeEdge.js | 12 ++- src/data/Graph.js | 10 ++- test/force.html | 3 + test/force2.html | 2 +- test/force3.html | 81 +++++++++++++++++++++ 10 files changed, 183 insertions(+), 31 deletions(-) create mode 100644 test/force3.html diff --git a/src/chart/graph/GraphSeries.js b/src/chart/graph/GraphSeries.js index 3fb713bab..8e71e397a 100644 --- a/src/chart/graph/GraphSeries.js +++ b/src/chart/graph/GraphSeries.js @@ -130,8 +130,9 @@ define(function (require) { symbol: 'circle', symbolSize: 10, - // roam: false, + draggable: false, + roam: false, roamDetail: { x: 0, y: 0, diff --git a/src/chart/graph/GraphView.js b/src/chart/graph/GraphView.js index a4fb62fb8..7916f6d0a 100644 --- a/src/chart/graph/GraphView.js +++ b/src/chart/graph/GraphView.js @@ -81,9 +81,30 @@ define(function (require) { this._updateController(seriesModel, coordSys, api); clearTimeout(this._layoutTimeout); - if (seriesModel.forceLayout) { - this._startForceLayoutIteration(seriesModel.forceLayout); + + var forceLayout = seriesModel.forceLayout; + if (forceLayout) { + this._startForceLayoutIteration(forceLayout); } + + // Update draggable + data.eachItemGraphicEl(function (el, idx) { + var draggable = data.getItemModel(idx).get('draggable'); + if (draggable && forceLayout) { + el.on('drag', function () { + forceLayout.warmUp(); + forceLayout.setFixed(idx); + // Write position back to layout + data.setItemLayout(idx, el.position); + }).on('dragend', function () { + forceLayout.setUnfixed(idx); + }); + } + else { + el.off('drag'); + } + el.setDraggable(draggable); + }, this); }, _startForceLayoutIteration: function (forceLayout) { diff --git a/src/chart/graph/forceHelper.js b/src/chart/graph/forceHelper.js index 772e8684d..a9c071862 100644 --- a/src/chart/graph/forceHelper.js +++ b/src/chart/graph/forceHelper.js @@ -55,15 +55,23 @@ define(function (require) { // Formula in 'Graph Drawing by Force-directed Placement' // var k = scale * Math.sqrt(width * height / nodes.length); - // var k2 = k * k; var friction = 0.6; return { warmUp: function () { - friction = 0.7; + friction = 0.5; + }, + + setFixed: function (idx) { + nodes[idx].fixed = true; }, + + setUnfixed: function (idx) { + nodes[idx].fixed = false; + }, + step: function (cb) { var v12 = []; var nLen = nodes.length; @@ -77,18 +85,20 @@ define(function (require) { var w = n2.w / (n1.w + n2.w); vec2.normalize(v12, v12); - scaleAndAdd(n1.p, n1.p, v12, w * d * friction); - scaleAndAdd(n2.p, n2.p, v12, -(1 - w) * d * friction); + !n1.fixed && scaleAndAdd(n1.p, n1.p, v12, w * d * friction); + !n2.fixed && scaleAndAdd(n2.p, n2.p, v12, -(1 - w) * d * friction); } // Gravity - // for (var i = 0; i < nLen; i++) { - // var n = nodes[i]; - // vec2.sub(v12, center, n.p); - // var d = vec2.len(v12); - // vec2.scale(v12, v12, 1 / d); - // var gravityFactor = gravity; - // vec2.scaleAndAdd(n.p, n.p, v12, gravity * friction); - // } + for (var i = 0; i < nLen; i++) { + var n = nodes[i]; + if (!n.fixed) { + vec2.sub(v12, center, n.p); + // var d = vec2.len(v12); + // vec2.scale(v12, v12, 1 / d); + // var gravityFactor = gravity; + vec2.scaleAndAdd(n.p, n.p, v12, gravity * friction); + } + } // Repulsive // PENDING @@ -104,22 +114,23 @@ define(function (require) { d = 1; } var repFact = (n1.rep + n2.rep) / d / d; - scaleAndAdd(n1.pp, n1.pp, v12, repFact); - scaleAndAdd(n2.pp, n2.pp, v12, -repFact); + !n1.fixed && scaleAndAdd(n1.pp, n1.pp, v12, repFact); + !n2.fixed && scaleAndAdd(n2.pp, n2.pp, v12, -repFact); } } var v = []; for (var i = 0; i < nLen; i++) { var n = nodes[i]; - vec2.sub(v, n.p, n.pp); - vec2.scaleAndAdd(n.p, n.p, v, friction); - - vec2.copy(n.pp, n.p); + if (!n.fixed) { + vec2.sub(v, n.p, n.pp); + vec2.scaleAndAdd(n.p, n.p, v, friction); + vec2.copy(n.pp, n.p); + } } - friction = friction * 0.99; + friction = friction * 0.995; - cb && cb(nodes, edges, friction < 0.002); + cb && cb(nodes, edges, friction < 0.01); } }; }; diff --git a/src/chart/graph/forceLayout.js b/src/chart/graph/forceLayout.js index 18c49ab2e..d2c0282d4 100644 --- a/src/chart/graph/forceLayout.js +++ b/src/chart/graph/forceLayout.js @@ -4,6 +4,7 @@ define(function (require) { var numberUtil = require('../../util/number'); var simpleLayoutHelper = require('./simpleLayoutHelper'); var circularLayoutHelper = require('./circularLayoutHelper'); + var vec2 = require('zrender/core/vector'); return function (ecModel, api) { ecModel.eachSeriesByType('graph', function (graphSeries) { @@ -60,10 +61,17 @@ define(function (require) { }); var oldStep = forceInstance.step; forceInstance.step = function (cb) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i].fixed) { + // Write back to layout instance + vec2.copy(nodes[i].p, graph.getNodeByIndex(i).getLayout()); + } + } oldStep(function (nodes, edges, stopped) { for (var i = 0, l = nodes.length; i < l; i++) { - var node = graph.getNodeByIndex(i); - node.setLayout(nodes[i].p); + if (!nodes[i].fixed) { + graph.getNodeByIndex(i).setLayout(nodes[i].p); + } preservedPoints[nodeData.getId(i)] = nodes[i].p; } for (var i = 0, l = edges.length; i < l; i++) { @@ -86,6 +94,9 @@ define(function (require) { }; graphSeries.forceLayout = forceInstance; graphSeries.preservedPoints = preservedPoints; + + // Step to get the layout + forceInstance.step(); } else { // Remove prev injected forceLayout instance diff --git a/src/chart/helper/Symbol.js b/src/chart/helper/Symbol.js index b1e79cd67..353a78d80 100644 --- a/src/chart/helper/Symbol.js +++ b/src/chart/helper/Symbol.js @@ -29,6 +29,10 @@ define(function (require) { var symbolProto = Symbol.prototype; + function driftSymbol(dx, dy) { + this.parent.drift(dx, dy); + } + symbolProto._createSymbol = function (symbolType, data, idx) { // Remove paths created before this.removeAll(); @@ -47,6 +51,8 @@ define(function (require) { z2: 100, scale: [0, 0] }); + // Rewrite drift method + symbolPath.drift = driftSymbol; var size = normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize')); @@ -99,6 +105,12 @@ define(function (require) { symbolPath.zlevel = zlevel; symbolPath.z = z; }; + + symbolProto.setDraggable = function (draggable) { + var symbolPath = this.childAt(0); + symbolPath.draggable = draggable; + symbolPath.cursor = draggable ? 'move' : 'pointer'; + }; /** * Update symbol properties * @param {module:echarts/data/List} data diff --git a/src/chart/helper/createGraphFromNodeEdge.js b/src/chart/helper/createGraphFromNodeEdge.js index 3cd3e1e90..982d1b11d 100644 --- a/src/chart/helper/createGraphFromNodeEdge.js +++ b/src/chart/helper/createGraphFromNodeEdge.js @@ -13,10 +13,14 @@ define(function (require) { } var linkNameList = []; + var validEdges = []; for (var i = 0; i < edges.length; i++) { var link = edges[i]; - linkNameList[i] = zrUtil.retrieve(link.id, link.source + ' - ' + link.target); - graph.addEdge(link.source, link.target, i); + // addEdge may fail when source or target not exists + if (graph.addEdge(link.source, link.target, i)) { + validEdges.push(link); + linkNameList.push(zrUtil.retrieve(link.id, link.source + ' - ' + link.target)); + } } // FIXME @@ -26,11 +30,13 @@ define(function (require) { var edgeData = new List(['value'], hostModel); nodeData.initData(nodes); - edgeData.initData(edges, linkNameList); + edgeData.initData(validEdges, linkNameList); graph.setEdgeData(edgeData); linkList.linkToGraph(nodeData, graph); + // Update dataIndex of nodes and edges because invalid edge may be removed + graph.update(); return graph; }; diff --git a/src/data/Graph.js b/src/data/Graph.js index e397f8e27..d4f97999c 100644 --- a/src/data/Graph.js +++ b/src/data/Graph.js @@ -81,8 +81,13 @@ define(function(require) { graphProto.addNode = function (id, dataIndex) { var nodesMap = this._nodesMap; + // Assign dataIndex as id if not exists + if (id == null) { + id = dataIndex; + } + if (nodesMap[id]) { - return nodesMap[id]; + return; } var node = new Node(id, dataIndex); @@ -134,8 +139,9 @@ define(function(require) { } var key = n1.id + '-' + n2.id; + // PENDING if (edgesMap[key]) { - return edgesMap[key]; + return; } var edge = new Edge(n1, n2, dataIndex); diff --git a/test/force.html b/test/force.html index d88b8770a..be1dffdc6 100644 --- a/test/force.html +++ b/test/force.html @@ -77,6 +77,9 @@ height: '25%', force: { // initLayout: 'circular' + // gravity: 0 + repulsion: 100, + edgeLength: 5 }, edges: item.edges.map(function (e) { return { diff --git a/test/force2.html b/test/force2.html index 1bd765056..c3075bdbc 100644 --- a/test/force2.html +++ b/test/force2.html @@ -52,7 +52,6 @@ node.x = node.y = null; }); chart.setOption({ - tooltip: {}, legend: [{ // selectedMode: 'single', data: categories.map(function (a) { @@ -71,6 +70,7 @@ categories: categories, animation: false, roam: true, + draggable: true, force: { repulsion: 100 }, diff --git a/test/force3.html b/test/force3.html new file mode 100644 index 000000000..484334e00 --- /dev/null +++ b/test/force3.html @@ -0,0 +1,81 @@ + + + + + + + + + + +
+ + + \ No newline at end of file -- GitLab