|
@ -1,4 +1,6 @@ |
|
|
var util = require('../../util'); |
|
|
|
|
|
|
|
|
let util = require('../../util'); |
|
|
|
|
|
|
|
|
|
|
|
import NetworkUtil from '../NetworkUtil'; |
|
|
|
|
|
|
|
|
class View { |
|
|
class View { |
|
|
constructor(body, canvas) { |
|
|
constructor(body, canvas) { |
|
@ -29,80 +31,25 @@ class View { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Find the center position of the network |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
_getRange(specificNodes = []) { |
|
|
|
|
|
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; |
|
|
|
|
|
if (specificNodes.length > 0) { |
|
|
|
|
|
for (var i = 0; i < specificNodes.length; i++) { |
|
|
|
|
|
node = this.body.nodes[specificNodes[i]]; |
|
|
|
|
|
if (minX > (node.shape.boundingBox.left)) { |
|
|
|
|
|
minX = node.shape.boundingBox.left; |
|
|
|
|
|
} |
|
|
|
|
|
if (maxX < (node.shape.boundingBox.right)) { |
|
|
|
|
|
maxX = node.shape.boundingBox.right; |
|
|
|
|
|
} |
|
|
|
|
|
if (minY > (node.shape.boundingBox.top)) { |
|
|
|
|
|
minY = node.shape.boundingBox.top; |
|
|
|
|
|
} // top is negative, bottom is positive
|
|
|
|
|
|
if (maxY < (node.shape.boundingBox.bottom)) { |
|
|
|
|
|
maxY = node.shape.boundingBox.bottom; |
|
|
|
|
|
} // top is negative, bottom is positive
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
for (var i = 0; i < this.body.nodeIndices.length; i++) { |
|
|
|
|
|
node = this.body.nodes[this.body.nodeIndices[i]]; |
|
|
|
|
|
if (minX > (node.shape.boundingBox.left)) { |
|
|
|
|
|
minX = node.shape.boundingBox.left; |
|
|
|
|
|
} |
|
|
|
|
|
if (maxX < (node.shape.boundingBox.right)) { |
|
|
|
|
|
maxX = node.shape.boundingBox.right; |
|
|
|
|
|
} |
|
|
|
|
|
if (minY > (node.shape.boundingBox.top)) { |
|
|
|
|
|
minY = node.shape.boundingBox.top; |
|
|
|
|
|
} // top is negative, bottom is positive
|
|
|
|
|
|
if (maxY < (node.shape.boundingBox.bottom)) { |
|
|
|
|
|
maxY = node.shape.boundingBox.bottom; |
|
|
|
|
|
} // top is negative, bottom is positive
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) { |
|
|
|
|
|
minY = 0, maxY = 0, minX = 0, maxX = 0; |
|
|
|
|
|
} |
|
|
|
|
|
return {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; |
|
|
|
|
|
* @returns {{x: number, y: number}} |
|
|
|
|
|
* @private |
|
|
|
|
|
*/ |
|
|
|
|
|
_findCenter(range) { |
|
|
|
|
|
return {x: (0.5 * (range.maxX + range.minX)), |
|
|
|
|
|
y: (0.5 * (range.maxY + range.minY))}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* This function zooms out to fit all data on screen based on amount of nodes |
|
|
* This function zooms out to fit all data on screen based on amount of nodes |
|
|
* @param {Object} Options |
|
|
* @param {Object} Options |
|
|
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; |
|
|
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; |
|
|
*/ |
|
|
*/ |
|
|
fit(options = {nodes:[]}, initialZoom = false) { |
|
|
fit(options = {nodes:[]}, initialZoom = false) { |
|
|
var range; |
|
|
|
|
|
var zoomLevel; |
|
|
|
|
|
|
|
|
let range; |
|
|
|
|
|
let zoomLevel; |
|
|
|
|
|
if (options.nodes === undefined || options.nodes.length === 0) { |
|
|
|
|
|
options.nodes = this.body.nodeIndices; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (initialZoom === true) { |
|
|
if (initialZoom === true) { |
|
|
// check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
|
|
|
// check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
|
|
|
var positionDefined = 0; |
|
|
|
|
|
for (var nodeId in this.body.nodes) { |
|
|
|
|
|
|
|
|
let positionDefined = 0; |
|
|
|
|
|
for (let nodeId in this.body.nodes) { |
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
if (this.body.nodes.hasOwnProperty(nodeId)) { |
|
|
var node = this.body.nodes[nodeId]; |
|
|
|
|
|
|
|
|
let node = this.body.nodes[nodeId]; |
|
|
if (node.predefinedPosition === true) { |
|
|
if (node.predefinedPosition === true) { |
|
|
positionDefined += 1; |
|
|
positionDefined += 1; |
|
|
} |
|
|
} |
|
@ -113,24 +60,24 @@ class View { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
range = this._getRange(options.nodes); |
|
|
|
|
|
|
|
|
range = NetworkUtil._getRange(this.body.nodes, options.nodes); |
|
|
|
|
|
|
|
|
var numberOfNodes = this.body.nodeIndices.length; |
|
|
|
|
|
|
|
|
let numberOfNodes = this.body.nodeIndices.length; |
|
|
zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
|
|
|
zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
|
|
|
|
|
|
|
|
|
// correct for larger canvasses.
|
|
|
// correct for larger canvasses.
|
|
|
var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); |
|
|
|
|
|
|
|
|
let factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600); |
|
|
zoomLevel *= factor; |
|
|
zoomLevel *= factor; |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
|
this.body.emitter.emit("_resizeNodes"); |
|
|
this.body.emitter.emit("_resizeNodes"); |
|
|
range = this._getRange(options.nodes); |
|
|
|
|
|
|
|
|
range = NetworkUtil._getRange(this.body.nodes, options.nodes); |
|
|
|
|
|
|
|
|
var xDistance = Math.abs(range.maxX - range.minX) * 1.1; |
|
|
|
|
|
var yDistance = Math.abs(range.maxY - range.minY) * 1.1; |
|
|
|
|
|
|
|
|
let xDistance = Math.abs(range.maxX - range.minX) * 1.1; |
|
|
|
|
|
let yDistance = Math.abs(range.maxY - range.minY) * 1.1; |
|
|
|
|
|
|
|
|
var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; |
|
|
|
|
|
var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; |
|
|
|
|
|
|
|
|
let xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance; |
|
|
|
|
|
let yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance; |
|
|
|
|
|
|
|
|
zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel; |
|
|
zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel; |
|
|
} |
|
|
} |
|
@ -142,8 +89,8 @@ class View { |
|
|
zoomLevel = 1.0; |
|
|
zoomLevel = 1.0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var center = this._findCenter(range); |
|
|
|
|
|
var animationOptions = {position: center, scale: zoomLevel, animation: options.animation}; |
|
|
|
|
|
|
|
|
let center = NetworkUtil._findCenter(range); |
|
|
|
|
|
let animationOptions = {position: center, scale: zoomLevel, animation: options.animation}; |
|
|
this.moveTo(animationOptions); |
|
|
this.moveTo(animationOptions); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -157,7 +104,7 @@ class View { |
|
|
*/ |
|
|
*/ |
|
|
focus(nodeId, options = {}) { |
|
|
focus(nodeId, options = {}) { |
|
|
if (this.body.nodes[nodeId] !== undefined) { |
|
|
if (this.body.nodes[nodeId] !== undefined) { |
|
|
var nodePosition = {x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y}; |
|
|
|
|
|
|
|
|
let nodePosition = {x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y}; |
|
|
options.position = nodePosition; |
|
|
options.position = nodePosition; |
|
|
options.lockedOnNode = nodeId; |
|
|
options.lockedOnNode = nodeId; |
|
|
|
|
|
|
|
@ -229,9 +176,9 @@ class View { |
|
|
// set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
|
|
|
// set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
|
|
|
// but at least then we'll have the target transition
|
|
|
// but at least then we'll have the target transition
|
|
|
this.body.view.scale = this.targetScale; |
|
|
this.body.view.scale = this.targetScale; |
|
|
var viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight}); |
|
|
|
|
|
|
|
|
let viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight}); |
|
|
|
|
|
|
|
|
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
|
|
|
|
|
|
|
|
|
let distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
|
|
|
x: viewCenter.x - options.position.x, |
|
|
x: viewCenter.x - options.position.x, |
|
|
y: viewCenter.y - options.position.y |
|
|
y: viewCenter.y - options.position.y |
|
|
}; |
|
|
}; |
|
@ -268,14 +215,14 @@ class View { |
|
|
* @private |
|
|
* @private |
|
|
*/ |
|
|
*/ |
|
|
_lockedRedraw() { |
|
|
_lockedRedraw() { |
|
|
var nodePosition = {x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y}; |
|
|
|
|
|
var viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight}); |
|
|
|
|
|
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
|
|
|
|
|
|
|
|
|
let nodePosition = {x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y}; |
|
|
|
|
|
let viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight}); |
|
|
|
|
|
let distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
|
|
|
x: viewCenter.x - nodePosition.x, |
|
|
x: viewCenter.x - nodePosition.x, |
|
|
y: viewCenter.y - nodePosition.y |
|
|
y: viewCenter.y - nodePosition.y |
|
|
}; |
|
|
}; |
|
|
var sourceTranslation = this.body.view.translation; |
|
|
|
|
|
var targetTranslation = { |
|
|
|
|
|
|
|
|
let sourceTranslation = this.body.view.translation; |
|
|
|
|
|
let targetTranslation = { |
|
|
x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, |
|
|
x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x, |
|
|
y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y |
|
|
y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y |
|
|
}; |
|
|
}; |
|
@ -300,7 +247,7 @@ class View { |
|
|
this.easingTime += this.animationSpeed; |
|
|
this.easingTime += this.animationSpeed; |
|
|
this.easingTime = finished === true ? 1.0 : this.easingTime; |
|
|
this.easingTime = finished === true ? 1.0 : this.easingTime; |
|
|
|
|
|
|
|
|
var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); |
|
|
|
|
|
|
|
|
let progress = util.easingFunctions[this.animationEasingFunction](this.easingTime); |
|
|
|
|
|
|
|
|
this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; |
|
|
this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress; |
|
|
this.body.view.translation = { |
|
|
this.body.view.translation = { |
|
|