function _updateLinkShapes() { var v = vec2.create(); var right = vec2.create(1, 0); for (var i = 0, len = filteredLinks.length; i < len; i++) { var link = filteredLinks[i]; var linkShape = linkShapes[i]; var sourceShape = nodeShapes[link.source]; var targetShape = nodeShapes[link.target]; linkShape.style.xStart = sourceShape.position[0]; linkShape.style.yStart = sourceShape.position[1]; linkShape.style.xEnd = targetShape.position[0]; linkShape.style.yEnd = targetShape.position[1]; if (forceSerie.linkSymbol) { var arrowShape = arrowShapes[i]; vec2.copy(arrowShape.position, targetShape.position); vec2.sub(v, sourceShape.position, targetShape.position); vec2.normalize(v, v); vec2.scaleAndAdd( arrowShape.position, arrowShape.position, v, targetShape.style.r + 2 ); if (v[1] < 0) { var angle = 2 * Math.PI - Math.acos(-v[0]); } else { var angle = Math.acos(-v[0]); } arrowShape.rotation = angle - Math.PI / 2; } } }
_updateLinkShapes: function() { var v = vec2.create(); var n = vec2.create(); var p1 = vec2.create(); var p2 = vec2.create(); var edges = this._graph.edges; for (var i = 0, len = edges.length; i < len; i++) { var edge = edges[i]; var sourceShape = edge.node1.shape; var targetShape = edge.node2.shape; vec2.copy(p1, sourceShape.position); vec2.copy(p2, targetShape.position); var edgeShapeStyle = edge.shape.style; vec2.sub(v, p1, p2); vec2.normalize(v, v); if (edgeShapeStyle.offset) { n[0] = v[1]; n[1] = - v[0]; vec2.scaleAndAdd(p1, p1, n, edgeShapeStyle.offset); vec2.scaleAndAdd(p2, p2, n, edgeShapeStyle.offset); } else if (edge.shape.type === 'bezier-curve') { edgeShapeStyle.cpX1 = (p1[0] + p2[0]) / 2 - (p2[1] - p1[1]) / 4; edgeShapeStyle.cpY1 = (p1[1] + p2[1]) / 2 - (p1[0] - p2[0]) / 4; } edgeShapeStyle.xStart = p1[0]; edgeShapeStyle.yStart = p1[1]; edgeShapeStyle.xEnd = p2[0]; edgeShapeStyle.yEnd = p2[1]; edge.shape.modSelf(); if (edge.shape._symbolShape) { var symbolShape = edge.shape._symbolShape; vec2.copy(symbolShape.position, p2); vec2.scaleAndAdd( symbolShape.position, symbolShape.position, v, targetShape.style.width / 2 + 2 ); var angle = Math.atan2(v[1], v[0]); symbolShape.rotation = Math.PI / 2 - angle; symbolShape.modSelf(); } } },
_syncNodePositions: function () { var graph = this._graph; for (var i = 0; i < graph.nodes.length; i++) { var gNode = graph.nodes[i]; var position = gNode.layout.position; var node = gNode.data; var shape = gNode.shape; var fixX = shape.fixed || node.fixX; var fixY = shape.fixed || node.fixY; if (fixX === true) { fixX = 1; } else if (isNaN(fixX)) { fixX = 0; } if (fixY === true) { fixY = 1; } else if (isNaN(fixY)) { fixY = 0; } shape.position[0] += (position[0] - shape.position[0]) * (1 - fixX); shape.position[1] += (position[1] - shape.position[1]) * (1 - fixY); vec2.copy(position, shape.position); var nodeName = node.name; if (nodeName) { var gPos = this.__nodePositionMap[nodeName]; if (!gPos) { gPos = this.__nodePositionMap[nodeName] = vec2.create(); } vec2.copy(gPos, position); } shape.modSelf(); } },
TreeLayout.prototype._updateNodeXPosition = function (node) { var minX = Infinity; var maxX = -Infinity; node.layout.position = node.layout.position || vec2.create(); for (var i = 0; i < node.children.length; i++) { var child = node.children[i]; this._updateNodeXPosition(child); var x = child.layout.position[0]; if (x < minX) { minX = x; } if (x > maxX) { maxX = x; } } if (node.children.length > 0) { node.layout.position[0] = (minX + maxX) / 2; } else { node.layout.position[0] = 0; } var off = this._layerOffsets[node.depth] || 0; if (off > node.layout.position[0]) { var shift = off - node.layout.position[0]; this._shiftSubtree(node, shift); for (var i = node.depth + 1; i < node.height + node.depth; i++) { this._layerOffsets[i] += shift; } } this._layerOffsets[node.depth] = node.layout.position[0] + node.layout.width + this.nodePadding; this._layers[node.depth].push(node); };
_update: function(e) { this._layout.temperature = this._temperature; this._layout.update(); for (var i = 0; i < this._layout.nodes.length; i++) { var position = this._layout.nodes[i].position; var shape = this._nodeShapes[i]; var node = this._filteredNodes[i]; if (shape.fixed || (node.fixX && node.fixY)) { vec2.copy(position, shape.position); } else if (node.fixX) { position[0] = shape.position[0]; shape.position[1] = position[1]; } else if (node.fixY) { position[1] = shape.position[1]; shape.position[0] = position[0]; } else { vec2.copy(shape.position, position); } var nodeName = node.name; if (nodeName) { var gPos = this.__nodePositionMap[nodeName]; if (!gPos) { gPos = this.__nodePositionMap[nodeName] = vec2.create(); } vec2.copy(gPos, position); } } this._temperature *= this._coolDown; },
_syncNodePositions: function() { var graph = this._graph; for (var i = 0; i < graph.nodes.length; i++) { var gNode = graph.nodes[i]; var position = gNode.layout.position; var node = gNode.data; var shape = gNode.shape; if (shape.fixed || (node.fixX && node.fixY)) { vec2.copy(position, shape.position); } else if (node.fixX) { position[0] = shape.position[0]; shape.position[1] = position[1]; } else if (node.fixY) { position[1] = shape.position[1]; shape.position[0] = position[0]; } else { vec2.copy(shape.position, position); } var nodeName = node.name; if (nodeName) { var gPos = this.__nodePositionMap[nodeName]; if (!gPos) { gPos = this.__nodePositionMap[nodeName] = vec2.create(); } vec2.copy(gPos, position); } this.zr.modShape(shape.id); } },
/**************************** * Class: ForceLayout ***************************/ function ForceLayout() { this.barnesHutOptimize = false; this.barnesHutTheta = 1.5; this.repulsionByDegree = false; this.preventOverlap = false; this.strongGravity = true; this.gravity = 1.0; this.scaling = 1.0; this.edgeWeightInfluence = 1.0; this.center = [0, 0]; this.width = 500; this.height = 500; this.maxSpeedIncrease = 1.0; this.nodes = []; this.edges = []; this.bbox = new ArrayCtor(4); this._rootRegion = new Region(); this._rootRegion.centerOfMass = vec2.create(); this._massArr = null; this._k = 0; }
_updateWorker: function(e) { if (!this._updating) { return; } var positionArr = new Float32Array(e.data); var token = positionArr[0]; var ret = token === this._token; // If token is from current layout instance if (ret) { var nNodes = (positionArr.length - 1) / 2; for (var i = 0; i < nNodes; i++) { var shape = this._nodeShapes[i]; var node = this._filteredNodes[i]; var x = positionArr[i * 2 + 1]; var y = positionArr[i * 2 + 2]; if (shape.fixed || (node.fixX && node.fixY)) { positionArr[i * 2 + 1] = shape.position[0]; positionArr[i * 2 + 2] = shape.position[1]; } else if (node.fixX) { positionArr[i * 2 + 1] = shape.position[0]; shape.position[1] = y; } else if (node.fixY) { positionArr[i * 2 + 2] = shape.position[1]; shape.position[0] = x; } else { shape.position[0] = x; shape.position[1] = y; } var nodeName = node.name; if (nodeName) { var gPos = this.__nodePositionMap[nodeName]; if (!gPos) { gPos = this.__nodePositionMap[nodeName] = vec2.create(); } vec2.copy(gPos, shape.position); } } this._layoutWorker.postMessage(positionArr.buffer, [positionArr.buffer]); } var self = this; self._layoutWorker.postMessage({ cmd: 'update', steps: this._steps, temperature: this._temperature, coolDown: this._coolDown }); for (var i = 0; i < this._steps; i++) { this._temperature *= this._coolDown; } return ret; },
ForceLayout.prototype.applyEdgeAttraction = (function() { var v = vec2.create(); return function applyEdgeAttraction(edge) { var na = edge.source; var nb = edge.target; vec2.sub(v, na.position, nb.position); var d = vec2.len(v); var w; if (this.edgeWeightInfluence === 0) { w = 1; } else if (this.edgeWeightInfluence == 1) { w = edge.weight; } else { w = Math.pow(edge.weight, this.edgeWeightInfluence); } var factor; if (this.preventOverlap) { d = d - na.size - nb.size; if (d <= 0) { // No attraction return; } } var factor = -w * d / this._k; vec2.scaleAndAdd(na.force, na.force, v, factor); vec2.scaleAndAdd(nb.force, nb.force, v, -factor); }; })();
/**************************** * Class: Graph Node ***************************/ function GraphNode() { this.position = vec2.create(); this.force = vec2.create(); this.forcePrev = vec2.create(); this.speed = vec2.create(); this.speedPrev = vec2.create(); // If repulsionByDegree is true // mass = inDegree + outDegree + 1 // Else // mass is manually set this.mass = 1; this.inDegree = 0; this.outDegree = 0; }
ForceLayout.prototype.applyNodeStrongGravity = (function() { var v = vec2.create(); return function(node) { // vec2.negate(v, node.position); vec2.sub(v, this.center, node.position); var d = vec2.len(v) / 100; vec2.scaleAndAdd(node.force, node.force, v, d * this.gravity * node.mass); } })();
_syncNodePositions: function() { var graph = this._graph; // var delta = 0; for (var i = 0; i < graph.nodes.length; i++) { var gNode = graph.nodes[i]; var position = gNode.layout.position; var node = gNode.data; var shape = gNode.shape; // delta += vec2.len(shape.position, position); if (shape.fixed || (node.fixX && node.fixY)) { vec2.copy(position, shape.position); } else if (node.fixX) { position[0] = shape.position[0]; shape.position[1] = position[1]; } else if (node.fixY) { position[1] = shape.position[1]; shape.position[0] = position[0]; } else if (isNaN(node.fixX - 0) == false && isNaN(node.fixY - 0) == false) { shape.position[0] += (position[0] - shape.position[0]) * node.fixX; position[0] = shape.position[0]; shape.position[1] += (position[1] - shape.position[1]) * node.fixY; position[1] = shape.position[1]; } else if (isNaN(node.fixX - 0) == false) { shape.position[0] += (position[0] - shape.position[0]) * node.fixX; position[0] = shape.position[0]; shape.position[1] = position[1]; } else if (isNaN(node.fixY - 0) == false) { shape.position[1] += (position[1] - shape.position[1]) * node.fixY; position[1] = shape.position[1]; shape.position[0] = position[0]; } else { vec2.copy(shape.position, position); } var nodeName = node.name; if (nodeName) { var gPos = this.__nodePositionMap[nodeName]; if (!gPos) { gPos = this.__nodePositionMap[nodeName] = vec2.create(); } vec2.copy(gPos, position); } shape.modSelf(); } // if (delta < 1) { // All shape stopped moving // this._layout.temperature = 0; // } },
ForceLayout.prototype.applyNodeGravity = (function() { var v = vec2.create(); return function(node) { // PENDING Move to centerOfMass or [0, 0] ? // vec2.sub(v, this._rootRegion.centerOfMass, node.position); // vec2.negate(v, node.position); vec2.sub(v, this.center, node.position); var d = vec2.len(v); vec2.scaleAndAdd(node.force, node.force, v, this.gravity * node.mass / (d + 1)); } })();
Region.prototype._updateCenterOfMass = function(node) { // Incrementally update if (this.centerOfMass == null) { this.centerOfMass = vec2.create(); } var x = this.centerOfMass[0] * this.mass; var y = this.centerOfMass[1] * this.mass; x += node.position[0] * node.mass; y += node.position[1] * node.mass; this.mass += node.mass; this.centerOfMass[0] = x / this.mass; this.centerOfMass[1] = y / this.mass; };
ForceLayout.prototype.applyRegionToNodeRepulsion = (function() { var v = vec2.create(); return function applyRegionToNodeRepulsion(region, node) { if (region.node) { // Region is a leaf this.applyNodeToNodeRepulsion(region.node, node, true); } else { vec2.sub(v, node.position, region.centerOfMass); var d2 = v[0] * v[0] + v[1] * v[1]; if (d2 > this.barnesHutTheta * region.size * region.size) { var factor = this._k * this._k * (node.mass + region.mass) / (d2 + 1); vec2.scaleAndAdd(node.force, node.force, v, factor * 2); } else { for (var i = 0; i < region.nSubRegions; i++) { this.applyRegionToNodeRepulsion(region.subRegions[i], node); } } } }; })();
_updateLinkShapes: function() { var v = vec2.create(); var links = this._filteredLinks; for (var i = 0, len = links.length; i < len; i++) { var link = links[i]; var linkShape = this._linkShapes[i]; var sourceShape = this._nodeShapes[link.source]; var targetShape = this._nodeShapes[link.target]; linkShape.style.xStart = sourceShape.position[0]; linkShape.style.yStart = sourceShape.position[1]; linkShape.style.xEnd = targetShape.position[0]; linkShape.style.yEnd = targetShape.position[1]; this.zr.modShape(linkShape.id); if (linkShape._symbolShape) { var symbolShape = linkShape._symbolShape; vec2.copy(symbolShape.position, targetShape.position); vec2.sub(v, sourceShape.position, targetShape.position); vec2.normalize(v, v); vec2.scaleAndAdd( symbolShape.position, symbolShape.position, v, targetShape.style.width / 2 + 2 ); var angle; if (v[1] < 0) { angle = 2 * Math.PI - Math.acos(-v[0]); } else { angle = Math.acos(-v[0]); } symbolShape.rotation = angle - Math.PI / 2; this.zr.modShape(symbolShape.id); } } },
_updateLinkShapes: function() { var v = vec2.create(); var edges = this._graph.edges; for (var i = 0, len = edges.length; i < len; i++) { var edge = edges[i]; var sourceShape = edge.node1.shape; var targetShape = edge.node2.shape; edge.shape.style.xStart = sourceShape.position[0]; edge.shape.style.yStart = sourceShape.position[1]; edge.shape.style.xEnd = targetShape.position[0]; edge.shape.style.yEnd = targetShape.position[1]; this.zr.modShape(edge.shape.id); if (edge.shape._symbolShape) { var symbolShape = edge.shape._symbolShape; vec2.copy(symbolShape.position, targetShape.position); vec2.sub(v, sourceShape.position, targetShape.position); vec2.normalize(v, v); vec2.scaleAndAdd( symbolShape.position, symbolShape.position, v, targetShape.style.width / 2 + 2 ); var angle; if (v[1] < 0) { angle = 2 * Math.PI - Math.acos(-v[0]); } else { angle = Math.acos(-v[0]); } symbolShape.rotation = angle - Math.PI / 2; this.zr.modShape(symbolShape.id); } } },
ForceLayout.prototype.applyNodeToNodeRepulsion = (function() { var v = vec2.create(); return function applyNodeToNodeRepulsion(na, nb, oneWay) { if (na == nb) { return; } vec2.sub(v, na.position, nb.position); var d2 = v[0] * v[0] + v[1] * v[1]; // PENDING if (d2 === 0) { return; } var factor; var k2 = this._k * this._k; var mass = na.mass + nb.mass; if (this.preventOverlap) { var d = Math.sqrt(d2); d = d - na.size - nb.size; if (d > 0) { factor = k2 * mass / (d * d); } else if (d <= 0) { // A stronger repulsion if overlap factor = k2 * 10 * mass; } } else { // Divide factor by an extra `d` to normalize the `v` factor = k2 * mass / d2; } if (!oneWay) { vec2.scaleAndAdd(na.force, na.force, v, factor * 2); } vec2.scaleAndAdd(nb.force, nb.force, v, -factor * 2); }; })();
ForceLayout.prototype.applyNodeGravity = (function() { var v = vec2.create(); return function(node) { // PENDING Move to centerOfMass or [0, 0] ? // vec2.sub(v, this._rootRegion.centerOfMass, node.position); // vec2.negate(v, node.position); vec2.sub(v, this.center, node.position); if (this.width > this.height) { // Stronger gravity on y axis v[1] *= this.width / this.height; } else { // Stronger gravity on x axis v[0] *= this.height / this.width; } var d = vec2.len(v) / 100; if (this.strongGravity) { vec2.scaleAndAdd(node.force, node.force, v, d * this.gravity * node.mass); } else { vec2.scaleAndAdd(node.force, node.force, v, this.gravity * node.mass / (d + 1)); } }; })();
function _randomInSquare(x, y, size) { var v = vec2.create(); v[0] = (Math.random() - 0.5) * size + x; v[1] = (Math.random() - 0.5) * size + y; return v; }
function _update(stepTime) { var len = nodePositions.length; var v12 = []; // 计算节点之间斥力 var k2 = k*k; // Reset force for (var i = 0; i < len; i++) { nodeForces[i][0] = 0; nodeForces[i][1] = 0; } for (var i = 0; i < len; i++) { for (var j = i+1; j < len; j++){ var w1 = nodeWeights[i]; var w2 = nodeWeights[j]; var p1 = nodePositions[i]; var p2 = nodePositions[j]; // 节点1到2的向量 vec2.sub(v12, p2, p1); var d = vec2.length(v12); // 距离大于500忽略斥力 if(d > 500){ continue; } if(d < 5){ d = 5; } vec2.scale(v12, v12, 1 / d); var forceFactor = 1 * (w1 + w2) * k2 / d; //节点1受到的力 vec2.scaleAndAdd( nodeForces[i], nodeForces[i], v12, -forceFactor ); //节点2受到的力 vec2.scaleAndAdd( nodeForces[j], nodeForces[j], v12, forceFactor ); } } // 计算节点之间引力 for (var i = 0, l = filteredLinks.length; i < l; i++) { var link = filteredLinks[i]; var w = linkWeights[i]; var s = link.source; var t = link.target; var p1 = nodePositions[s]; var p2 = nodePositions[t]; vec2.sub(v12, p2, p1); var d2 = vec2.lengthSquare(v12); if (d2 === 0) { continue; } var forceFactor = w * d2 / k / Math.sqrt(d2); // 节点1受到的力 vec2.scaleAndAdd( nodeForces[s], nodeForces[s], v12, forceFactor ); // 节点2受到的力 vec2.scaleAndAdd( nodeForces[t], nodeForces[t], v12, -forceFactor ); } // 到质心的向心力 for (var i = 0, l = filteredNodes.length; i < l; i++){ var p = nodePositions[i]; vec2.sub(v12, centroid, p); var d2 = vec2.lengthSquare(v12); var forceFactor = d2 * centripetal / (100 * Math.sqrt(d2)); vec2.scaleAndAdd( nodeForces[i], nodeForces[i], v12, forceFactor ); } var velocity = []; // 计算位置(verlet积分) for (var i = 0, l = nodePositions.length; i < l; i++) { var name = filteredNodes[i].name; if (filteredNodes[i].fixed) { // 拖拽同步 vec2.set(nodePositions[i], mouseX, mouseY); vec2.set(nodePrePositions[i], mouseX, mouseY); vec2.set(nodeShapes[i].position, mouseX, mouseY); if (filteredNodes[i].initial !== undefined) { vec2.set(filteredNodes[i].initial, mouseX, mouseY); } if (nodeInitialPos[name] !== undefined) { vec2.set(nodeInitialPos[name], mouseX, mouseY); } continue; } var p = nodePositions[i]; var __P = nodePrePositions[i]; vec2.sub(velocity, p, __P); __P[0] = p[0]; __P[1] = p[1]; vec2.scaleAndAdd( velocity, velocity, nodeForces[i], stepTime / nodeMasses[i] ); // Damping vec2.scale(velocity, velocity, temperature); // 防止速度太大 velocity[0] = Math.max(Math.min(velocity[0], 100), -100); velocity[1] = Math.max(Math.min(velocity[1], 100), -100); vec2.add(p, p, velocity); vec2.copy(nodeShapes[i].position, p); if (name) { if (nodeInitialPos[name] === undefined) { nodeInitialPos[name] = vec2.create(); } vec2.copy(nodeInitialPos[name], p); } else { if (filteredNodes[i].initial === undefined) { filteredNodes[i].initial = vec2.create(); } vec2.copy(filteredNodes[i].initial, p); } // if(isNaN(p[0]) || isNaN(p[1])){ // throw new Error('NaN'); // } } }
function _buildNodeShapes(nodes, minRadius, maxRadius) { // 将值映射到minRadius-maxRadius的范围上 var radius = []; var l = nodes.length; for (var i = 0; i < l; i++) { var node = nodes[i]; if (node.value !== undefined) { radius.push(node.value); } else { radius.push(1); } } var narr = new NDArray(radius); radius = narr.map(minRadius, maxRadius) .toArray(); var max = narr.max(); if (max !== 0) { nodeWeights = narr.mul(1/max, narr).toArray(); } for (var i = 0; i < l; i++) { var node = nodes[i]; var x, y; var r = radius[i]; var initPos; if (node.initial !== undefined) { initPos = node.initial; } else if (nodeInitialPos[node.name] !== undefined) { initPos = nodeInitialPos[node.name]; } else { initPos = _randomInSquare( viewportWidth/2, viewportHeight/2, initSize ); } var x = initPos[0]; var y = initPos[1]; // 初始化位置 nodePositions[i] = vec2.create(x, y); nodePrePositions[i] = vec2.create(x, y); // 初始化受力 nodeForces[i] = vec2.create(0, 0); // 初始化质量 nodeMasses[i] = r * r * density * 0.035; var shape = { id : zr.newShapeId(self.type), shape : 'circle', style : { r : r, x : 0, y : 0 }, clickable : true, highlightStyle : {}, position : [x, y], __forceIndex : i }; // Label var labelStyle; if (self.query(forceSerie, 'itemStyle.normal.label.show') ) { shape.style.text = node.name; shape.style.textPosition = 'inside'; labelStyle = self.query( forceSerie, 'itemStyle.normal.label.textStyle' ) || {}; shape.style.textColor = labelStyle.color || '#fff'; shape.style.textAlign = labelStyle.align || 'center'; shape.style.textBaseline = labelStyle.baseline || 'middle'; shape.style.textFont = self.getFont(labelStyle); } if (self.query(forceSerie, 'itemStyle.emphasis.label.show')) { shape.highlightStyle.text = node.name; shape.highlightStyle.textPosition = 'inside'; labelStyle = self.query( forceSerie, 'itemStyle.emphasis.label.textStyle' ) || {}; shape.highlightStyle.textColor = labelStyle.color || '#fff'; shape.highlightStyle.textAlign = labelStyle.align || 'center'; shape.highlightStyle.textBaseline = labelStyle.baseline || 'middle'; shape.highlightStyle.textFont = self.getFont(labelStyle); } // 优先级 node.style > category.style > defaultStyle zrUtil.merge(shape.style, nodeStyle); zrUtil.merge(shape.highlightStyle, nodeEmphasisStyle); if (typeof(node.category) !== 'undefined') { var category = categories[node.category]; if (category) { if (legend) { shape.style.color = legend.getColor(category.name); } var style = category.itemStyle; if (style) { if (style.normal) { zrUtil.merge(shape.style, style.normal, { overwrite : true }); } if (style.emphasis) { zrUtil.merge( shape.highlightStyle, style.emphasis, { overwrite : true } ); } } } } if (typeof(node.itemStyle) !== 'undefined') { var style = node.itemStyle; if(style.normal ){ zrUtil.merge(shape.style, style.normal, { overwrite : true }); } if(style.normal ){ zrUtil.merge(shape.highlightStyle, style.emphasis, { overwrite : true }); } } // 拖拽特性 self.setCalculable(shape); shape.dragEnableTime = 0; shape.ondragstart = self.shapeHandler.ondragstart; shape.draggable = true; nodeShapes.push(shape); self.shapeList.push(shape); var categoryName = ''; if (typeof(node.category) !== 'undefined') { var category = categories[node.category]; categoryName = (category && category.name) || ''; } // !!Pack data before addShape ecData.pack( shape, // category { name : categoryName }, // series index 0, // data node, // data index zrUtil.indexOf(rawNodes, node), // name node.name || '', // value node.value ); zr.addShape(shape); } // _normalize(nodeMasses, nodeMasses); }
_initLayout: function(serie) { var graph = this._graph; var len = graph.nodes.length; var minRadius = this.query(serie, 'minRadius'); var maxRadius = this.query(serie, 'maxRadius'); this._steps = serie.steps || 1; this._layout.center = this.parseCenter(this.zr, serie.center); this._layout.width = this.parsePercent(serie.size, this.zr.getWidth()); this._layout.height = this.parsePercent(serie.size, this.zr.getHeight()); this._layout.large = serie.large; this._layout.scaling = serie.scaling; this._layout.ratioScaling = serie.ratioScaling; this._layout.gravity = serie.gravity; this._layout.temperature = 1; this._layout.coolDown = serie.coolDown; // 将值映射到minRadius-maxRadius的范围上 var min = Infinity; var max = -Infinity; for (var i = 0; i < len; i++) { var gNode = graph.nodes[i]; gNode.layout = { radius: gNode.data.value || 1, mass: 0 }; max = Math.max(gNode.data.value, max); min = Math.min(gNode.data.value, min); } var divider = max - min; for (var i = 0; i < len; i++) { var gNode = graph.nodes[i]; if (divider > 0) { gNode.layout.radius = (gNode.layout.radius - min) * (maxRadius - minRadius) / divider + minRadius; // 节点质量是归一的 gNode.layout.mass = gNode.layout.radius / maxRadius; } else { gNode.layout.radius = (maxRadius - minRadius) / 2; gNode.layout.mass = 0.5; } } for (var i = 0; i < len; i++) { // var initPos; var gNode = graph.nodes[i]; if (typeof(this.__nodePositionMap[gNode.name]) !== 'undefined') { gNode.layout.position = vec2.create(); vec2.copy(gNode.layout.position, this.__nodePositionMap[gNode.name]); } else if (typeof(gNode.data.initial) !== 'undefined') { gNode.layout.position = vec2.create(); vec2.copy(gNode.layout.position, gNode.data.initial); } else { var center = this._layout.center; var size = Math.min(this._layout.width, this._layout.height); gNode.layout.position = _randomInSquare( center[0], center[1], size * 0.8 ); } var style = gNode.shape.style; var radius = gNode.layout.radius; style.width = style.width || (radius * 2); style.height = style.height || (radius * 2); style.x = -style.width / 2; style.y = -style.height / 2; vec2.copy(gNode.shape.position, gNode.layout.position); } // 边 len = graph.edges.length; max = -Infinity; for (var i = 0; i < len; i++) { var e = graph.edges[i]; e.layout = { weight: e.data.weight || 1 }; if (e.layout.weight > max) { max = e.layout.weight; } } // 权重归一 for (var i = 0; i < len; i++) { var e = graph.edges[i]; e.layout.weight /= max; } this._layout.init(graph, serie.useWorker); },
ForceLayout.prototype.update = function() { var nNodes = this.nodes.length; this.updateBBox(); this._k = 0.4 * this.scaling * Math.sqrt(this.width * this.height / nNodes); if (this.barnesHutOptimize) { this._rootRegion.setBBox( this.bbox[0], this.bbox[1], this.bbox[2], this.bbox[3] ); this._rootRegion.beforeUpdate(); for (var i = 0; i < nNodes; i++) { this._rootRegion.addNode(this.nodes[i]); } this._rootRegion.afterUpdate(); } else { // Update center of mass of whole graph var mass = 0; var centerOfMass = this._rootRegion.centerOfMass; vec2.set(centerOfMass, 0, 0); for (var i = 0; i < nNodes; i++) { var node = this.nodes[i]; mass += node.mass; vec2.scaleAndAdd(centerOfMass, centerOfMass, node.position, node.mass); } vec2.scale(centerOfMass, centerOfMass, 1 / mass); } // Reset forces for (var i = 0; i < nNodes; i++) { var node = this.nodes[i]; vec2.copy(node.forcePrev, node.force); vec2.copy(node.speedPrev, node.speed); vec2.set(node.force, 0, 0); } // Compute forces // Repulsion for (var i = 0; i < nNodes; i++) { var na = this.nodes[i]; if (this.barnesHutOptimize) { this.applyRegionToNodeRepulsion(this._rootRegion, na); } else { for (var j = i + 1; j < nNodes; j++) { var nb = this.nodes[j]; this.applyNodeToNodeRepulsion(na, nb, false); } } // Gravity if (this.gravity > 0) { this.applyNodeGravity(na); } } // Attraction for (var i = 0; i < this.edges.length; i++) { this.applyEdgeAttraction(this.edges[i]); } // Apply forces // var speed = vec2.create(); var v = vec2.create(); for (var i = 0; i < nNodes; i++) { var node = this.nodes[i]; var speed = node.speed; // var swing = vec2.dist(node.force, node.forcePrev); // // var swing = 30; // vec2.scale(node.force, node.force, 1 / (1 + Math.sqrt(swing))); vec2.scale(node.force, node.force, 1 / 30); // contraint force var df = vec2.len(node.force) + 0.1; var scale = Math.min(df, 500.0) / df; vec2.scale(node.force, node.force, scale); vec2.add(speed, speed, node.force); vec2.scale(speed, speed, this.temperature); // Prevent swinging // Limited the increase of speed up to 100% each step // TODO adjust by nodes number vec2.sub(v, speed, node.speedPrev); var swing = vec2.len(v); if (swing > 0) { vec2.scale(v, v, 1 / swing); var base = vec2.len(node.speedPrev); if (base > 0) { swing = Math.min(swing / base, this.maxSpeedIncrease) * base; vec2.scaleAndAdd(speed, node.speedPrev, v, swing); } } // constraint speed var ds = vec2.len(speed); var scale = Math.min(ds, 100.0) / (ds + 0.1); vec2.scale(speed, speed, scale); vec2.add(node.position, node.position, speed); } };
Intro.prototype._circle = function (cb) { var self = this; var graphMain = this._kgraph.getComponentByType('GRAPH'); var zr = graphMain.getZR(); var layer = zr.painter.getLayer(0); var graph = graphMain.getGraph(); var width = zr.getWidth(); var height = zr.getHeight(); var circles = graphMain.getCircles(); if (!circles.length) { cb(); return; } var circle = circles[0]; var center = vec2.create(); // 定位到circle中间 for (var i = 0; i < circle.nodes.length; i++) { var pos = circle.nodes[i].entity.el.position; circle.nodes[i].entity.setZLevel(10); vec2.add(center, center, pos); } vec2.scale(center, center, 1 / circle.nodes.length); var edge; var rightMost = -Infinity; // 查找到定位最右的边 for (var i = 0; i < circle.edges.length; i++) { var e = circle.edges[i]; e.entity.setZLevel(10); var x = (e.entity.el.style.xStart + e.entity.el.style.xEnd) / 2; if (x > rightMost) { rightMost = x; edge = e; } } graphMain.moveTo(width / 2 - center[0], height / 2 - center[1], showTip); function showTip() { var y = (edge.entity.el.style.yStart + edge.entity.el.style.yEnd) / 2; self._$tip.style.display = 'block'; self._$tip.className = 'bkg-tip bkg-tip-circle'; self._$tip.style.left = rightMost + layer.position[0] + 10 +'px'; self._$tip.style.top = y + layer.position[1] - self._$tip.clientHeight +'px'; } return function () { self._$tip.style.display = 'none'; for (var i = 0; i < circle.nodes.length; i++) { circle.nodes[i].entity.setZLevel(1); } for (var i = 0; i < circle.edges.length; i++) { circle.edges[i].entity.setZLevel(0); } zr.refreshNextFrame(); } };
define(function (require) { var Entity = require('./Entity'); var BezierCurveShape = require('zrender/shape/BezierCurve'); var Group = require('zrender/Group'); var zrUtil = require('zrender/tool/util'); var curveTool = require('zrender/tool/curve'); var LabelCurveShape = require('../shape/LabelCurve'); var EdgeEntity = require('./Edge'); var util = require('../util/util'); var intersect = require('../util/intersect'); var vec2 = require('zrender/tool/vector'); var v1 = vec2.create(); var v2 = vec2.create(); var v3 = vec2.create(); var min = vec2.create(); var max = vec2.create(); function lerp(x0, x1, t) { return x0 * (1 - t) + x1 * t; } var ExtraEdgeEntity = function (opts) { Entity.call(this); // Configs opts = opts || {}; this.sourceEntity = opts.sourceEntity || null; this.targetEntity = opts.targetEntity || null; this.label = opts.label || ''; this.style = { color: '#0e90fe', opacity: 0.8, hidden: true }; this.highlightStyle = { color: '#f9dd05', opacity: 1, hidden: false }; if (opts.style) { zrUtil.merge(this.style, opts.style) } if (opts.highlightStyle) { zrUtil.merge(this.highlightStyle, opts.highlightStyle) } var self = this; this.el = new LabelCurveShape({ style: { xStart: 0, yStart: 0, xEnd: 0, yEnd: 0, cpX1: 0, cpY1: 0, lineWidth: 1, opacity: this.style.opacity, color: this.style.color, strokeColor: this.style.color, text: util.truncate(this.label, 10), textFont: '13px 微软雅黑', textPadding: 5 }, z: 0, zlevel: 0, clickable: true, onclick: function () { self.dispatch('click') }, onmouseover: function () { self.dispatch('mouseover'); }, onmouseout: function () { self.dispatch('mouseout'); } }); }; ExtraEdgeEntity.prototype.hidden = true; ExtraEdgeEntity.prototype.initialize = function (zr) { this.update(); }; ExtraEdgeEntity.prototype.setZLevel = function (zlevel) { this.el.zlevel = zlevel; this.el.modSelf(); }; ExtraEdgeEntity.prototype.update = function () { if (this.sourceEntity && this.targetEntity) { this._setCurvePoints( this.sourceEntity.el.position, this.targetEntity.el.position, 1, true ); } this.el.modSelf(); }; ExtraEdgeEntity.prototype.setStyle = function (name, value) { this.style[name] = value; switch (name) { case 'color': this.el.style.strokeColor = value; this.el.style.color = value; break; case 'lineWidth': this.el.style.lineWidth = value; break; case 'hidden': this.hidden = value; } } ExtraEdgeEntity.prototype.highlight = function () { this.hidden = this.highlightStyle.hidden; this.el.zlevel = 3; this.el.style.color = this.highlightStyle.color; this.el.style.strokeColor = this.highlightStyle.color; this.el.style.opacity = this.highlightStyle.opacity; this.el.modSelf(); this._isHighlight = true; }; ExtraEdgeEntity.prototype.lowlight = function () { this.hidden = this.style.hidden; this.el.zlevel = 0; this.el.style.color = this.style.color; this.el.style.strokeColor = this.style.color; this.el.style.opacity = this.style.opacity; this.el.modSelf(); this._isHighlight = false; }; ExtraEdgeEntity.prototype.animateLength = function (zr, time, delay, fromEntity, cb) { var inv = 1; if (fromEntity === this.targetEntity) { vec2.copy(v1, this.targetEntity.el.position); vec2.copy(v2, this.sourceEntity.el.position); inv = -1; } else { vec2.copy(v1, this.sourceEntity.el.position); vec2.copy(v2, this.targetEntity.el.position); } var self = this; var obj = {t: 0}; var curve = this.el; this._setCurvePoints(v1, v2, inv); var x0 = curve.style.xStart; var x1 = curve.style.cpX1; var x2 = curve.style.xEnd; var y0 = curve.style.yStart; var y1 = curve.style.cpY1; var y2 = curve.style.yEnd; this.addAnimation('length', zr.animation.animate(obj) .when(time || 1000, { t: 1 }) .during(function (target, t) { // Subdivide var x01 = lerp(x0, x1, t); var x12 = lerp(x1, x2, t); var x012 = lerp(x01, x12, t); var y01 = lerp(y0, y1, t); var y12 = lerp(y1, y2, t); var y012 = lerp(y01, y12, t); curve.style.cpX1 = x01; curve.style.cpY1 = y01; curve.style.xEnd = x012; curve.style.yEnd = y012; self.el.modSelf(); zr.refreshNextFrame(); }) .delay(delay) .done(function () { cb && cb(); }) .start() ); } ExtraEdgeEntity.prototype.highlightLabel = EdgeEntity.prototype.highlightLabel; ExtraEdgeEntity.prototype.lowlightLabel = EdgeEntity.prototype.lowlightLabel; ExtraEdgeEntity.prototype.animateTextPadding = EdgeEntity.prototype.animateTextPadding; ExtraEdgeEntity.prototype._setCurvePoints = function (p1, p2, inv) { var sourceEntity = this.sourceEntity; var targetEntity = this.targetEntity; var curve = this.el; curve.style.xStart = p1[0]; curve.style.yStart = p1[1]; curve.style.xEnd = p2[0]; curve.style.yEnd = p2[1]; curve.style.cpX1 = (p1[0] + p2[0]) / 2 - inv * (p2[1] - p1[1]) / 4; curve.style.cpY1 = (p1[1] + p2[1]) / 2 - inv * (p1[0] - p2[0]) / 4; curve.style.r = ( sourceEntity.radius + targetEntity.radius ) / 20 + 3; curve.style.cx = curveTool.quadraticAt( curve.style.xStart, curve.style.cpX1, curve.style.xEnd, 0.5 ); curve.style.cy = curveTool.quadraticAt( curve.style.yStart, curve.style.cpY1, curve.style.yEnd, 0.5 ); inv = inv || 1; } ExtraEdgeEntity.prototype.intersectRect = function (rect) { return intersect.curveRect(this.el.style, rect); } ExtraEdgeEntity.prototype.isInsideRect = function (rect) { var style = this.el.style; vec2.set(v2, style.cpX1, style.cpY1); vec2.set(v3, style.xEnd, style.yEnd); vec2.set(min, style.xStart, style.yStart); vec2.set(max, style.xStart, style.yStart); vec2.min(min, min, v2); vec2.min(min, min, v3); vec2.max(max, max, v2); vec2.max(max, max, v3); return !(max[0] < rect.x || max[1] < rect.y || min[0] > (rect.x + rect.width) || min[1] > (rect.y + rect.height)); } zrUtil.inherits(ExtraEdgeEntity, Entity); return ExtraEdgeEntity; });
_initLayout: function(serie) { var nodes = this._filteredNodes; var links = this._filteredLinks; var shapes = this._nodeShapes; var len = nodes.length; var minRadius = this.query(serie, 'minRadius'); var maxRadius = this.query(serie, 'maxRadius'); this._steps = serie.steps || 1; this._coolDown = serie.coolDown || 0.99; var center = this.parseCenter(this.zr, serie.center); var width = this.parsePercent(serie.size, this.zr.getWidth()); var height = this.parsePercent(serie.size, this.zr.getHeight()); var size = Math.min(width, height); // 将值映射到minRadius-maxRadius的范围上 var radius = []; for (var i = 0; i < len; i++) { var node = nodes[i]; radius.push(node.value || 1); } var arr = new NDArray(radius); radius = arr.map(minRadius, maxRadius).toArray(); var max = arr.max(); if (max === 0) { return; } var massArr = arr.mul(1/max, arr).toArray(); var positionArr = new ArrayCtor(len * 2); for (var i = 0; i < len; i++) { var initPos; var node = nodes[i]; if (typeof(this.__nodePositionMap[node.name]) !== 'undefined') { initPos = vec2.create(); vec2.copy(initPos, this.__nodePositionMap[node.name]); } else if (typeof(node.initial) !== 'undefined') { initPos = Array.prototype.slice.call(node.initial); } else { initPos = _randomInSquare( center[0], center[1], size * 0.8 ); } var style = shapes[i].style; style.width = style.width || (radius[i] * 2); style.height = style.height || (radius[i] * 2); style.x = -style.width / 2; style.y = -style.height / 2; shapes[i].position = initPos; positionArr[i * 2] = initPos[0]; positionArr[i * 2 + 1] = initPos[1]; } len = links.length; var edgeArr = new ArrayCtor(len * 2); var edgeWeightArr = new ArrayCtor(len); for (var i = 0; i < len; i++) { var link = links[i]; edgeArr[i * 2] = link.source; edgeArr[i * 2 + 1] = link.target; edgeWeightArr[i] = link.weight || 1; } arr = new NDArray(edgeWeightArr); var max = arr.max(); if (max === 0) { return; } var edgeWeightArr = arr.mul(1 / max, arr)._array; var config = { center: center, width: serie.ratioScaling ? width : size, height: serie.ratioScaling ? height : size, scaling: serie.scaling || 1.0, gravity: serie.gravity || 1.0, barnesHutOptimize: serie.large }; if (this._layoutWorker) { this._token = getToken(); this._layoutWorker.postMessage({ cmd: 'init', nodesPosition: positionArr, nodesMass: massArr, nodesSize: radius, edges: edgeArr, edgesWeight: edgeWeightArr, token: this._token }); this._layoutWorker.postMessage({ cmd: 'updateConfig', config: config }); } else { zrUtil.merge(this._layout, config, true); this._layout.initNodes(positionArr, massArr, radius); this._layout.initEdges(edgeArr, edgeWeightArr); } },