提交 db308726 编写于 作者: L lang

Add force layout

上级 456cffcc
......@@ -29,6 +29,8 @@ define(function (require) {
var trailLength = seriesModel.get('effect.trailLength');
var zr = api.getZr();
// Avoid the drag cause ghost shadow
// FIXME Better way ?
zr.painter.getLayer(zlevel).clear(true);
// Config layer with motion blur
if (this._lastZlevel != null) {
......
......@@ -17,6 +17,7 @@ define(function (require) {
echarts.registerLayout(require('./graph/simpleLayout'));
echarts.registerLayout(require('./graph/circularLayout'));
echarts.registerLayout(require('./graph/forceLayout'));
// Graph view coordinate system
echarts.registerCoordinateSystem('graphView', {
......
......@@ -112,6 +112,11 @@ define(function (require) {
layout: null,
// Configuration of force
force: {
initLayout: null
},
x: 'center',
y: 'center',
x2: null,
......
......@@ -62,7 +62,6 @@ define(function (require) {
});
});
// Save the original lineWidth
data.graph.eachEdge(function (edge) {
edge.__lineWidth = edge.getModel('lineStyle.normal').get('width');
......@@ -80,6 +79,20 @@ define(function (require) {
this._updateNodeAndLinkScale();
this._updateController(seriesModel, coordSys, api);
if (seriesModel.forceLayout) {
this._startForceLayoutIteration(seriesModel.forceLayout);
}
},
_startForceLayoutIteration: function (forceLayout) {
var self = this;
(function step() {
forceLayout.step(function (stopped) {
self.updateLayout();
!stopped && setTimeout(step, 16);
});
})();
},
_updateController: function (seriesModel, coordSys, api) {
......
define(function (require) {
var circularLayoutHelper = require('./circularLayoutHelper');
return function (ecModel, api) {
ecModel.eachSeriesByType('graph', function (seriesModel) {
if (seriesModel.get('layout') === 'circular') {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type !== 'view') {
return;
}
var rect = coordSys.getBoundingRect();
var nodeData = seriesModel.getData();
var graph = nodeData.graph;
var angle = 0;
var unitAngle = Math.PI * 2 / nodeData.getSum('value');
var cx = rect.width / 2 + rect.x;
var cy = rect.height / 2 + rect.y;
var r = Math.min(rect.width, rect.height) / 2;
graph.eachNode(function (node) {
var value = node.getValue('value');
angle += unitAngle * value / 2;
node.setLayout([
r * Math.cos(angle) + cx,
r * Math.sin(angle) + cy
]);
angle += unitAngle * value / 2;
});
graph.eachEdge(function (edge) {
var curveness = edge.getModel().get('lineStyle.normal.curveness');
var p1 = edge.node1.getLayout();
var p2 = edge.node2.getLayout();
var cp1;
if (curveness > 0) {
cp1 = [cx, cy];
}
edge.setLayout([p1, p2, cp1]);
});
circularLayoutHelper(seriesModel);
}
});
};
......
define(function (require) {
return function (seriesModel) {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type !== 'view') {
return;
}
var rect = coordSys.getBoundingRect();
var nodeData = seriesModel.getData();
var graph = nodeData.graph;
var angle = 0;
var unitAngle = Math.PI * 2 / nodeData.getSum('value');
var cx = rect.width / 2 + rect.x;
var cy = rect.height / 2 + rect.y;
var r = Math.min(rect.width, rect.height) / 2;
graph.eachNode(function (node) {
var value = node.getValue('value');
angle += unitAngle * value / 2;
node.setLayout([
r * Math.cos(angle) + cx,
r * Math.sin(angle) + cy
]);
angle += unitAngle * value / 2;
});
graph.eachEdge(function (edge) {
var curveness = edge.getModel().get('lineStyle.normal.curveness') || 0;
var p1 = edge.node1.getLayout();
var p2 = edge.node2.getLayout();
var cp1;
if (curveness > 0) {
cp1 = [cx, cy];
}
edge.setLayout([p1, p2, cp1]);
});
};
});
\ No newline at end of file
define(function (require) {
var vec2 = require('zrender/core/vector');
var scaleAndAdd = vec2.scaleAndAdd;
function adjacentNode(n, e) {
return e.n1 === n ? e.n2 : e.n1;
}
return function (nodes, edges, opts) {
var width = opts.width || 500;
var height = opts.height || 500;
var center = opts.center || [width / 2, height / 2];
var scale = opts.scale || 1;
var gravity = opts.gravity == null ? 1 : opts.gravity;
for (var i = 0; i < edges.length; i++) {
var e = edges[i];
e.n1.edges = e.n1.edges || [];
e.n2.edges = e.n2.edges || [];
e.n1.edges.push(e);
e.n2.edges.push(e);
}
// Init position
for (var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if (!n.p) {
// Use the position from first adjecent node with defined position
// Or use a random position
// From d3
var j = -1;
while (++j < n.edges.length) {
var e = n.edges[j];
var other = adjacentNode(n, e);
if (other.p) {
n.p = vec2.clone(other.p);
break;
}
}
if (!n.p) {
n.p = vec2.create(
width * (Math.random() - 0.5) + center[0],
height * (Math.random() - 0.5) + center[1]
);
}
}
n.pp = vec2.clone(n.p);
n.f = [0, 0];
n.edges = null;
}
// Formula in 'Graph Drawing by Force-directed Placement'
var k = 0.5 * scale * Math.sqrt(width * height / nodes.length);
var k2 = k * k;
var friction = 1;
return {
warmUp: function () {
friction = 0.7;
},
step: function (cb) {
var v12 = [];
var nLen = nodes.length;
// Reset
for (var i = 0; i < nLen; i++) {
vec2.set(nodes[i].f, 0, 0);
}
// Repulsive
for (var i = 0; i < nLen; i++) {
var n1 = nodes[i];
for (var j = i + 1; j < nLen; j++) {
var n2 = nodes[j];
vec2.sub(v12, n2.p, n1.p);
var d = vec2.len(v12);
// Ignore repulse larger than 500
if(d > 500) {
continue;
}
if(d < 5) {
d = 5;
}
// k * k / d
var repFact = (n1.w + n2.w) * k2 / d / d;
scaleAndAdd(n1.f, n1.f, v12, -repFact);
scaleAndAdd(n2.f, n2.f, v12, repFact);
}
}
for (var i = 0; i < edges.length; i++) {
var e = edges[i];
var n1 = e.n1;
var n2 = e.n2;
vec2.sub(v12, n2.p, n1.p);
var d2 = vec2.lenSquare(v12);
if (d2 === 0) {
continue;
}
// d * d / k
var attrFact = e.w * Math.sqrt(d2) / k;
scaleAndAdd(n1.f, n1.f, v12, attrFact);
scaleAndAdd(n2.f, n2.f, v12, -attrFact);
}
// Gravity
for (var i = 0; i < nLen; i++){
var n = nodes[i];
vec2.sub(v12, center, n.p);
var forceFactor = vec2.len(v12) * gravity / 1000;
vec2.scaleAndAdd(n.f, n.f, v12, forceFactor);
}
var v = [];
for (var i = 0; i < nLen; i++) {
var n = nodes[i];
vec2.sub(v, n.p, n.pp);
vec2.add(v, v, vec2.scale(n.f, n.f, friction / 100));
vec2.add(n.p, n.p, v);
vec2.copy(n.pp, n.p);
}
friction = friction * 0.99;
cb && cb(nodes, edges, friction < 0.012);
}
};
};
});
\ No newline at end of file
define(function (require) {
var forceHelper = require('./forceHelper');
var numberUtil = require('../../util/number');
var simpleLayoutHelper = require('./simpleLayoutHelper');
var circularLayoutHelper = require('./circularLayoutHelper');
return function (ecModel, api) {
ecModel.eachSeriesByType('graph', function (graphSeries) {
if (graphSeries.get('layout') === 'force') {
var forceModel = graphSeries.getModel('force');
var initLayout = forceModel.get('initLayout');
if (!initLayout || initLayout === 'none') {
simpleLayoutHelper(graphSeries);
}
else if (initLayout === 'circular') {
circularLayoutHelper(graphSeries);
}
var graph = graphSeries.getGraph();
var nodeData = graph.data;
var edgeData = graph.edgeData;
var nodeDataExtent = nodeData.getDataExtent('value');
var edgeDataExtent = edgeData.getDataExtent('value');
var nodes = nodeData.mapArray('value', function (value, idx) {
var point = nodeData.getItemLayout(idx);
var w = numberUtil.linearMap(value, nodeDataExtent, [0, 1]);
return {
w: isNaN(w) ? 0.5 : w,
p: (isNaN(point[0]) || isNaN(point[1])) ? null : point
};
});
var edges = edgeData.mapArray('value', function (value, idx) {
var edge = graph.getEdgeByIndex(idx);
var w = numberUtil.linearMap(value, edgeDataExtent, [0, 1]);
return {
w: isNaN(w) ? 0.5 : w,
n1: nodes[edge.node1.dataIndex],
n2: nodes[edge.node2.dataIndex],
curveness: edge.getModel().get('lineStyle.normal.curveness') || 0
};
});
var coordSys = graphSeries.coordinateSystem;
var rect = coordSys.getBoundingRect();
var forceInstance = forceHelper(nodes, edges, {
width: rect.width,
height: rect.height,
center: [rect.x + rect.width / 2, rect.y + rect.height / 2]
});
var oldStep = forceInstance.step;
forceInstance.step = function (cb) {
oldStep(function (nodes, edges, stopped) {
for (var i = 0, l = nodes.length; i < l; i++) {
graph.getNodeByIndex(i).setLayout(nodes[i].p);
}
for (var i = 0, l = edges.length; i < l; i++) {
var e = edges[i];
var p1 = e.n1.p;
var p2 = e.n2.p;
var points = [p1, p2];
if (e.curveness > 0) {
points.push([
(p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * e.curveness,
(p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * e.curveness
]);
}
graph.getEdgeByIndex(i).setLayout(points);
}
// Update layout
cb && cb(stopped);
});
};
graphSeries.forceLayout = forceInstance;
}
else {
// Remove prev injected forceLayout instance
graphSeries.forceLayout = null;
}
});
};
});
\ No newline at end of file
define(function (require) {
var simpleLayoutHelper = require('./simpleLayoutHelper');
return function (ecModel, api) {
ecModel.eachSeriesByType('graph', function (seriesModel) {
var layout = seriesModel.get('layout');
if (!layout || layout === 'none') {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type !== 'view') {
return;
}
var graph = seriesModel.getGraph();
graph.eachNode(function (node) {
var model = node.getModel();
node.setLayout([+model.get('x'), +model.get('y')]);
});
graph.eachEdge(function (edge) {
var curveness = edge.getModel().get('lineStyle.normal.curveness');
var p1 = edge.node1.getLayout();
var p2 = edge.node2.getLayout();
var cp1;
if (curveness > 0) {
cp1 = [
(p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness,
(p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness
];
}
edge.setLayout([p1, p2, cp1]);
});
simpleLayoutHelper(seriesModel);
}
});
};
......
define(function (require) {
return function (seriesModel) {
var coordSys = seriesModel.coordinateSystem;
if (coordSys && coordSys.type !== 'view') {
return;
}
var graph = seriesModel.getGraph();
graph.eachNode(function (node) {
var model = node.getModel();
node.setLayout([+model.get('x'), +model.get('y')]);
});
graph.eachEdge(function (edge) {
var curveness = edge.getModel().get('lineStyle.normal.curveness') || 0;
var p1 = edge.node1.getLayout();
var p2 = edge.node2.getLayout();
var cp1;
if (curveness > 0) {
cp1 = [
(p1[0] + p2[0]) / 2 - (p1[1] - p2[1]) * curveness,
(p1[1] + p2[1]) / 2 - (p2[0] - p1[0]) * curveness
];
}
edge.setLayout([p1, p2, cp1]);
});
};
});
\ No newline at end of file
......@@ -51,6 +51,8 @@ define(function (require) {
var ty = quadraticDerivativeAt(p1[1], cp1[1], p2[1], t);
this.rotation = -Math.atan2(ty, tx) - Math.PI / 2;
this.ignore = false;
}
effectLineProto._updateEffectSymbol = function (lineData, idx) {
......@@ -68,6 +70,7 @@ define(function (require) {
symbol = symbolUtil.createSymbol(
symbolType, -0.5, -0.5, 1, 1, color
);
symbol.ignore = true;
symbol.z2 = 100;
this._symbolType = symbolType;
this._period = period;
......
......@@ -36,7 +36,10 @@ define(function (require) {
function createLine(points) {
var line = new LinePath({
name: 'line'
name: 'line',
style: {
strokeNoScale: true
}
});
setLinePoints(line.shape, points);
return line;
......
......@@ -94,6 +94,15 @@ define(function(require) {
return node;
};
/**
* Get node by data index
* @param {number} dataIndex
* @return {module:echarts/data/Graph~Node}
*/
graphProto.getNodeByIndex = function (dataIndex) {
var rawIdx = this.data.getRawIndex(dataIndex);
return this.nodes[rawIdx];
};
/**
* Get node by id
* @param {string} id
......@@ -148,7 +157,16 @@ define(function(require) {
};
/**
* Get edge by two nodes
* Get edge by data index
* @param {number} dataIndex
* @return {module:echarts/data/Graph~Node}
*/
graphProto.getEdgeByIndex = function (dataIndex) {
var rawIdx = this.edgeData.getRawIndex(dataIndex);
return this.edges[rawIdx];
};
/**
* Get edge by two linked nodes
* @param {module:echarts/data/Graph.Node|string} n1
* @param {module:echarts/data/Graph.Node|string} n2
* @return {module:echarts/data/Graph.Edge}
......@@ -254,11 +272,11 @@ define(function(require) {
};
// TODO
graphProto.depthFirstTraverse = function (
cb, startNode, direction, context
) {
// graphProto.depthFirstTraverse = function (
// cb, startNode, direction, context
// ) {
};
// };
// Filter update
graphProto.update = function () {
......
......@@ -64,7 +64,7 @@
}
},
animationDurationUpdate: 1500,
animationEasing: 'quinticInOut',
animationEasingUpdate: 'quinticInOut',
series : [
{
name: 'Les Miserables',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册