|
|
@ -23,43 +23,43 @@ |
|
|
|
* example for the color |
|
|
|
*/ |
|
|
|
function Node(properties, imagelist, grouplist, constants) { |
|
|
|
this.selected = false; |
|
|
|
this.selected = false; |
|
|
|
|
|
|
|
this.edges = []; // all edges connected to this node
|
|
|
|
this.group = constants.nodes.group; |
|
|
|
this.edges = []; // all edges connected to this node
|
|
|
|
this.group = constants.nodes.group; |
|
|
|
|
|
|
|
this.fontSize = constants.nodes.fontSize; |
|
|
|
this.fontFace = constants.nodes.fontFace; |
|
|
|
this.fontColor = constants.nodes.fontColor; |
|
|
|
this.fontSize = constants.nodes.fontSize; |
|
|
|
this.fontFace = constants.nodes.fontFace; |
|
|
|
this.fontColor = constants.nodes.fontColor; |
|
|
|
|
|
|
|
this.color = constants.nodes.color; |
|
|
|
this.color = constants.nodes.color; |
|
|
|
|
|
|
|
// set defaults for the properties
|
|
|
|
this.id = undefined; |
|
|
|
this.shape = constants.nodes.shape; |
|
|
|
this.image = constants.nodes.image; |
|
|
|
this.x = 0; |
|
|
|
this.y = 0; |
|
|
|
this.xFixed = false; |
|
|
|
this.yFixed = false; |
|
|
|
this.radius = constants.nodes.radius; |
|
|
|
this.radiusFixed = false; |
|
|
|
this.radiusMin = constants.nodes.radiusMin; |
|
|
|
this.radiusMax = constants.nodes.radiusMax; |
|
|
|
// set defaults for the properties
|
|
|
|
this.id = undefined; |
|
|
|
this.shape = constants.nodes.shape; |
|
|
|
this.image = constants.nodes.image; |
|
|
|
this.x = 0; |
|
|
|
this.y = 0; |
|
|
|
this.xFixed = false; |
|
|
|
this.yFixed = false; |
|
|
|
this.radius = constants.nodes.radius; |
|
|
|
this.radiusFixed = false; |
|
|
|
this.radiusMin = constants.nodes.radiusMin; |
|
|
|
this.radiusMax = constants.nodes.radiusMax; |
|
|
|
|
|
|
|
this.imagelist = imagelist; |
|
|
|
this.grouplist = grouplist; |
|
|
|
this.imagelist = imagelist; |
|
|
|
this.grouplist = grouplist; |
|
|
|
|
|
|
|
this.setProperties(properties, constants); |
|
|
|
this.setProperties(properties, constants); |
|
|
|
|
|
|
|
// mass, force, velocity
|
|
|
|
this.mass = 50; // kg (mass is adjusted for the number of connected edges)
|
|
|
|
this.fx = 0.0; // external force x
|
|
|
|
this.fy = 0.0; // external force y
|
|
|
|
this.vx = 0.0; // velocity x
|
|
|
|
this.vy = 0.0; // velocity y
|
|
|
|
this.minForce = constants.minForce; |
|
|
|
this.damping = 0.9; // damping factor
|
|
|
|
// mass, force, velocity
|
|
|
|
this.mass = 50; // kg (mass is adjusted for the number of connected edges)
|
|
|
|
this.fx = 0.0; // external force x
|
|
|
|
this.fy = 0.0; // external force y
|
|
|
|
this.vx = 0.0; // velocity x
|
|
|
|
this.vy = 0.0; // velocity y
|
|
|
|
this.minForce = constants.minForce; |
|
|
|
this.damping = 0.9; // damping factor
|
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -67,10 +67,10 @@ function Node(properties, imagelist, grouplist, constants) { |
|
|
|
* @param {Edge} edge |
|
|
|
*/ |
|
|
|
Node.prototype.attachEdge = function(edge) { |
|
|
|
if (this.edges.indexOf(edge) == -1) { |
|
|
|
this.edges.push(edge); |
|
|
|
} |
|
|
|
this._updateMass(); |
|
|
|
if (this.edges.indexOf(edge) == -1) { |
|
|
|
this.edges.push(edge); |
|
|
|
} |
|
|
|
this._updateMass(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -78,11 +78,11 @@ Node.prototype.attachEdge = function(edge) { |
|
|
|
* @param {Edge} edge |
|
|
|
*/ |
|
|
|
Node.prototype.detachEdge = function(edge) { |
|
|
|
var index = this.edges.indexOf(edge); |
|
|
|
if (index != -1) { |
|
|
|
this.edges.splice(index, 1); |
|
|
|
} |
|
|
|
this._updateMass(); |
|
|
|
var index = this.edges.indexOf(edge); |
|
|
|
if (index != -1) { |
|
|
|
this.edges.splice(index, 1); |
|
|
|
} |
|
|
|
this._updateMass(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -91,7 +91,7 @@ Node.prototype.detachEdge = function(edge) { |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Node.prototype._updateMass = function() { |
|
|
|
this.mass = 50 + 20 * this.edges.length; // kg
|
|
|
|
this.mass = 50 + 20 * this.edges.length; // kg
|
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -100,81 +100,81 @@ Node.prototype._updateMass = function() { |
|
|
|
* @param {Object} constants and object with default, global properties |
|
|
|
*/ |
|
|
|
Node.prototype.setProperties = function(properties, constants) { |
|
|
|
if (!properties) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// basic properties
|
|
|
|
if (properties.id != undefined) {this.id = properties.id;} |
|
|
|
if (properties.label != undefined) {this.label = properties.label;} |
|
|
|
if (properties.title != undefined) {this.title = properties.title;} |
|
|
|
if (properties.group != undefined) {this.group = properties.group;} |
|
|
|
if (properties.x != undefined) {this.x = properties.x;} |
|
|
|
if (properties.y != undefined) {this.y = properties.y;} |
|
|
|
if (properties.value != undefined) {this.value = properties.value;} |
|
|
|
|
|
|
|
if (this.id === undefined) { |
|
|
|
throw "Node must have an id"; |
|
|
|
} |
|
|
|
|
|
|
|
// copy group properties
|
|
|
|
if (this.group) { |
|
|
|
var groupObj = this.grouplist.get(this.group); |
|
|
|
for (var prop in groupObj) { |
|
|
|
if (groupObj.hasOwnProperty(prop)) { |
|
|
|
this[prop] = groupObj[prop]; |
|
|
|
} |
|
|
|
} |
|
|
|
if (!properties) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// basic properties
|
|
|
|
if (properties.id != undefined) {this.id = properties.id;} |
|
|
|
if (properties.label != undefined) {this.label = properties.label;} |
|
|
|
if (properties.title != undefined) {this.title = properties.title;} |
|
|
|
if (properties.group != undefined) {this.group = properties.group;} |
|
|
|
if (properties.x != undefined) {this.x = properties.x;} |
|
|
|
if (properties.y != undefined) {this.y = properties.y;} |
|
|
|
if (properties.value != undefined) {this.value = properties.value;} |
|
|
|
|
|
|
|
if (this.id === undefined) { |
|
|
|
throw "Node must have an id"; |
|
|
|
} |
|
|
|
|
|
|
|
// copy group properties
|
|
|
|
if (this.group) { |
|
|
|
var groupObj = this.grouplist.get(this.group); |
|
|
|
for (var prop in groupObj) { |
|
|
|
if (groupObj.hasOwnProperty(prop)) { |
|
|
|
this[prop] = groupObj[prop]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// individual shape properties
|
|
|
|
if (properties.shape != undefined) {this.shape = properties.shape;} |
|
|
|
if (properties.image != undefined) {this.image = properties.image;} |
|
|
|
if (properties.radius != undefined) {this.radius = properties.radius;} |
|
|
|
if (properties.color != undefined) {this.color = Node.parseColor(properties.color);} |
|
|
|
|
|
|
|
if (properties.fontColor != undefined) {this.fontColor = properties.fontColor;} |
|
|
|
if (properties.fontSize != undefined) {this.fontSize = properties.fontSize;} |
|
|
|
if (properties.fontFace != undefined) {this.fontFace = properties.fontFace;} |
|
|
|
|
|
|
|
// individual shape properties
|
|
|
|
if (properties.shape != undefined) {this.shape = properties.shape;} |
|
|
|
if (properties.image != undefined) {this.image = properties.image;} |
|
|
|
if (properties.radius != undefined) {this.radius = properties.radius;} |
|
|
|
if (properties.color != undefined) {this.color = Node.parseColor(properties.color);} |
|
|
|
|
|
|
|
if (this.image != undefined) { |
|
|
|
if (this.imagelist) { |
|
|
|
this.imageObj = this.imagelist.load(this.image); |
|
|
|
} |
|
|
|
else { |
|
|
|
throw "No imagelist provided"; |
|
|
|
} |
|
|
|
} |
|
|
|
if (properties.fontColor != undefined) {this.fontColor = properties.fontColor;} |
|
|
|
if (properties.fontSize != undefined) {this.fontSize = properties.fontSize;} |
|
|
|
if (properties.fontFace != undefined) {this.fontFace = properties.fontFace;} |
|
|
|
|
|
|
|
this.xFixed = this.xFixed || (properties.x != undefined); |
|
|
|
this.yFixed = this.yFixed || (properties.y != undefined); |
|
|
|
this.radiusFixed = this.radiusFixed || (properties.radius != undefined); |
|
|
|
|
|
|
|
if (this.shape == 'image') { |
|
|
|
this.radiusMin = constants.nodes.widthMin; |
|
|
|
this.radiusMax = constants.nodes.widthMax; |
|
|
|
if (this.image != undefined) { |
|
|
|
if (this.imagelist) { |
|
|
|
this.imageObj = this.imagelist.load(this.image); |
|
|
|
} |
|
|
|
|
|
|
|
// choose draw method depending on the shape
|
|
|
|
switch (this.shape) { |
|
|
|
case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break; |
|
|
|
case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break; |
|
|
|
case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break; |
|
|
|
case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break; |
|
|
|
// TODO: add diamond shape
|
|
|
|
case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break; |
|
|
|
case 'text': this.draw = this._drawText; this.resize = this._resizeText; break; |
|
|
|
case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break; |
|
|
|
case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break; |
|
|
|
case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break; |
|
|
|
case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break; |
|
|
|
case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break; |
|
|
|
default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break; |
|
|
|
else { |
|
|
|
throw "No imagelist provided"; |
|
|
|
} |
|
|
|
|
|
|
|
// reset the size of the node, this can be changed
|
|
|
|
this._reset(); |
|
|
|
} |
|
|
|
|
|
|
|
this.xFixed = this.xFixed || (properties.x != undefined); |
|
|
|
this.yFixed = this.yFixed || (properties.y != undefined); |
|
|
|
this.radiusFixed = this.radiusFixed || (properties.radius != undefined); |
|
|
|
|
|
|
|
if (this.shape == 'image') { |
|
|
|
this.radiusMin = constants.nodes.widthMin; |
|
|
|
this.radiusMax = constants.nodes.widthMax; |
|
|
|
} |
|
|
|
|
|
|
|
// choose draw method depending on the shape
|
|
|
|
switch (this.shape) { |
|
|
|
case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break; |
|
|
|
case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break; |
|
|
|
case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break; |
|
|
|
case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break; |
|
|
|
// TODO: add diamond shape
|
|
|
|
case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break; |
|
|
|
case 'text': this.draw = this._drawText; this.resize = this._resizeText; break; |
|
|
|
case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break; |
|
|
|
case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break; |
|
|
|
case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break; |
|
|
|
case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break; |
|
|
|
case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break; |
|
|
|
default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break; |
|
|
|
} |
|
|
|
|
|
|
|
// reset the size of the node, this can be changed
|
|
|
|
this._reset(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -184,51 +184,51 @@ Node.prototype.setProperties = function(properties, constants) { |
|
|
|
* @return {Object} colorObject |
|
|
|
*/ |
|
|
|
Node.parseColor = function(color) { |
|
|
|
var c; |
|
|
|
if (util.isString(color)) { |
|
|
|
c = { |
|
|
|
border: color, |
|
|
|
background: color, |
|
|
|
highlight: { |
|
|
|
border: color, |
|
|
|
background: color |
|
|
|
} |
|
|
|
}; |
|
|
|
// TODO: automatically generate a nice highlight color
|
|
|
|
var c; |
|
|
|
if (util.isString(color)) { |
|
|
|
c = { |
|
|
|
border: color, |
|
|
|
background: color, |
|
|
|
highlight: { |
|
|
|
border: color, |
|
|
|
background: color |
|
|
|
} |
|
|
|
}; |
|
|
|
// TODO: automatically generate a nice highlight color
|
|
|
|
} |
|
|
|
else { |
|
|
|
c = {}; |
|
|
|
c.background = color.background || 'white'; |
|
|
|
c.border = color.border || c.background; |
|
|
|
if (util.isString(color.highlight)) { |
|
|
|
c.highlight = { |
|
|
|
border: color.highlight, |
|
|
|
background: color.highlight |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
c = {}; |
|
|
|
c.background = color.background || 'white'; |
|
|
|
c.border = color.border || c.background; |
|
|
|
if (util.isString(color.highlight)) { |
|
|
|
c.highlight = { |
|
|
|
border: color.highlight, |
|
|
|
background: color.highlight |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
c.highlight = {}; |
|
|
|
c.highlight.background = color.highlight && color.highlight.background || c.background; |
|
|
|
c.highlight.border = color.highlight && color.highlight.border || c.border; |
|
|
|
} |
|
|
|
c.highlight = {}; |
|
|
|
c.highlight.background = color.highlight && color.highlight.background || c.background; |
|
|
|
c.highlight.border = color.highlight && color.highlight.border || c.border; |
|
|
|
} |
|
|
|
return c; |
|
|
|
} |
|
|
|
return c; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* select this node |
|
|
|
*/ |
|
|
|
Node.prototype.select = function() { |
|
|
|
this.selected = true; |
|
|
|
this._reset(); |
|
|
|
this.selected = true; |
|
|
|
this._reset(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* unselect this node |
|
|
|
*/ |
|
|
|
Node.prototype.unselect = function() { |
|
|
|
this.selected = false; |
|
|
|
this._reset(); |
|
|
|
this.selected = false; |
|
|
|
this._reset(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -236,8 +236,8 @@ Node.prototype.unselect = function() { |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Node.prototype._reset = function() { |
|
|
|
this.width = undefined; |
|
|
|
this.height = undefined; |
|
|
|
this.width = undefined; |
|
|
|
this.height = undefined; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -246,7 +246,7 @@ Node.prototype._reset = function() { |
|
|
|
* has been set. |
|
|
|
*/ |
|
|
|
Node.prototype.getTitle = function() { |
|
|
|
return this.title; |
|
|
|
return this.title; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -256,46 +256,46 @@ Node.prototype.getTitle = function() { |
|
|
|
* @returns {number} distance Distance to the border in pixels |
|
|
|
*/ |
|
|
|
Node.prototype.distanceToBorder = function (ctx, angle) { |
|
|
|
var borderWidth = 1; |
|
|
|
|
|
|
|
if (!this.width) { |
|
|
|
this.resize(ctx); |
|
|
|
} |
|
|
|
|
|
|
|
//noinspection FallthroughInSwitchStatementJS
|
|
|
|
switch (this.shape) { |
|
|
|
case 'circle': |
|
|
|
case 'dot': |
|
|
|
return this.radius + borderWidth; |
|
|
|
|
|
|
|
case 'ellipse': |
|
|
|
var a = this.width / 2; |
|
|
|
var b = this.height / 2; |
|
|
|
var w = (Math.sin(angle) * a); |
|
|
|
var h = (Math.cos(angle) * b); |
|
|
|
return a * b / Math.sqrt(w * w + h * h); |
|
|
|
|
|
|
|
// TODO: implement distanceToBorder for database
|
|
|
|
// TODO: implement distanceToBorder for triangle
|
|
|
|
// TODO: implement distanceToBorder for triangleDown
|
|
|
|
|
|
|
|
case 'box': |
|
|
|
case 'image': |
|
|
|
case 'text': |
|
|
|
default: |
|
|
|
if (this.width) { |
|
|
|
return Math.min( |
|
|
|
Math.abs(this.width / 2 / Math.cos(angle)), |
|
|
|
Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth; |
|
|
|
// TODO: reckon with border radius too in case of box
|
|
|
|
} |
|
|
|
else { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
var borderWidth = 1; |
|
|
|
|
|
|
|
if (!this.width) { |
|
|
|
this.resize(ctx); |
|
|
|
} |
|
|
|
|
|
|
|
//noinspection FallthroughInSwitchStatementJS
|
|
|
|
switch (this.shape) { |
|
|
|
case 'circle': |
|
|
|
case 'dot': |
|
|
|
return this.radius + borderWidth; |
|
|
|
|
|
|
|
case 'ellipse': |
|
|
|
var a = this.width / 2; |
|
|
|
var b = this.height / 2; |
|
|
|
var w = (Math.sin(angle) * a); |
|
|
|
var h = (Math.cos(angle) * b); |
|
|
|
return a * b / Math.sqrt(w * w + h * h); |
|
|
|
|
|
|
|
// TODO: implement distanceToBorder for database
|
|
|
|
// TODO: implement distanceToBorder for triangle
|
|
|
|
// TODO: implement distanceToBorder for triangleDown
|
|
|
|
|
|
|
|
case 'box': |
|
|
|
case 'image': |
|
|
|
case 'text': |
|
|
|
default: |
|
|
|
if (this.width) { |
|
|
|
return Math.min( |
|
|
|
Math.abs(this.width / 2 / Math.cos(angle)), |
|
|
|
Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth; |
|
|
|
// TODO: reckon with border radius too in case of box
|
|
|
|
} |
|
|
|
else { |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: implement calculation of distance to border for all shapes
|
|
|
|
// TODO: implement calculation of distance to border for all shapes
|
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -304,8 +304,8 @@ Node.prototype.distanceToBorder = function (ctx, angle) { |
|
|
|
* @param {number} fy Force in vertical direction |
|
|
|
*/ |
|
|
|
Node.prototype._setForce = function(fx, fy) { |
|
|
|
this.fx = fx; |
|
|
|
this.fy = fy; |
|
|
|
this.fx = fx; |
|
|
|
this.fy = fy; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -315,8 +315,8 @@ Node.prototype._setForce = function(fx, fy) { |
|
|
|
* @private |
|
|
|
*/ |
|
|
|
Node.prototype._addForce = function(fx, fy) { |
|
|
|
this.fx += fx; |
|
|
|
this.fy += fy; |
|
|
|
this.fx += fx; |
|
|
|
this.fy += fy; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -324,19 +324,19 @@ Node.prototype._addForce = function(fx, fy) { |
|
|
|
* @param {number} interval Time interval in seconds |
|
|
|
*/ |
|
|
|
Node.prototype.discreteStep = function(interval) { |
|
|
|
if (!this.xFixed) { |
|
|
|
var dx = -this.damping * this.vx; // damping force
|
|
|
|
var ax = (this.fx + dx) / this.mass; // acceleration
|
|
|
|
this.vx += ax / interval; // velocity
|
|
|
|
this.x += this.vx / interval; // position
|
|
|
|
} |
|
|
|
if (!this.xFixed) { |
|
|
|
var dx = -this.damping * this.vx; // damping force
|
|
|
|
var ax = (this.fx + dx) / this.mass; // acceleration
|
|
|
|
this.vx += ax / interval; // velocity
|
|
|
|
this.x += this.vx / interval; // position
|
|
|
|
} |
|
|
|
|
|
|
|
if (!this.yFixed) { |
|
|
|
var dy = -this.damping * this.vy; // damping force
|
|
|
|
var ay = (this.fy + dy) / this.mass; // acceleration
|
|
|
|
this.vy += ay / interval; // velocity
|
|
|
|
this.y += this.vy / interval; // position
|
|
|
|
} |
|
|
|
if (!this.yFixed) { |
|
|
|
var dy = -this.damping * this.vy; // damping force
|
|
|
|
var ay = (this.fy + dy) / this.mass; // acceleration
|
|
|
|
this.vy += ay / interval; // velocity
|
|
|
|
this.y += this.vy / interval; // position
|
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
@ -345,7 +345,7 @@ Node.prototype.discreteStep = function(interval) { |
|
|
|
* @return {boolean} true if fixed, false if not |
|
|
|
*/ |
|
|
|
Node.prototype.isFixed = function() { |
|
|
|
return (this.xFixed && this.yFixed); |
|
|
|
return (this.xFixed && this.yFixed); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -355,9 +355,9 @@ Node.prototype.isFixed = function() { |
|
|
|
*/ |
|
|
|
// TODO: replace this method with calculating the kinetic energy
|
|
|
|
Node.prototype.isMoving = function(vmin) { |
|
|
|
return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin || |
|
|
|
(!this.xFixed && Math.abs(this.fx) > this.minForce) || |
|
|
|
(!this.yFixed && Math.abs(this.fy) > this.minForce)); |
|
|
|
return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin || |
|
|
|
(!this.xFixed && Math.abs(this.fx) > this.minForce) || |
|
|
|
(!this.yFixed && Math.abs(this.fy) > this.minForce)); |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -365,7 +365,7 @@ Node.prototype.isMoving = function(vmin) { |
|
|
|
* @return {boolean} selected True if node is selected, else false |
|
|
|
*/ |
|
|
|
Node.prototype.isSelected = function() { |
|
|
|
return this.selected; |
|
|
|
return this.selected; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -373,7 +373,7 @@ Node.prototype.isSelected = function() { |
|
|
|
* @return {Number} value |
|
|
|
*/ |
|
|
|
Node.prototype.getValue = function() { |
|
|
|
return this.value; |
|
|
|
return this.value; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -383,9 +383,9 @@ Node.prototype.getValue = function() { |
|
|
|
* @return {Number} value |
|
|
|
*/ |
|
|
|
Node.prototype.getDistance = function(x, y) { |
|
|
|
var dx = this.x - x, |
|
|
|
dy = this.y - y; |
|
|
|
return Math.sqrt(dx * dx + dy * dy); |
|
|
|
var dx = this.x - x, |
|
|
|
dy = this.y - y; |
|
|
|
return Math.sqrt(dx * dx + dy * dy); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
@ -396,15 +396,15 @@ Node.prototype.getDistance = function(x, y) { |
|
|
|
* @param {Number} max |
|
|
|
*/ |
|
|
|
Node.prototype.setValueRange = function(min, max) { |
|
|
|
if (!this.radiusFixed && this.value !== undefined) { |
|
|
|
if (max == min) { |
|
|
|
this.radius = (this.radiusMin + this.radiusMax) / 2; |
|
|
|
} |
|
|
|
else { |
|
|
|
var scale = (this.radiusMax - this.radiusMin) / (max - min); |
|
|
|
this.radius = (this.value - min) * scale + this.radiusMin; |
|
|
|
} |
|
|
|
if (!this.radiusFixed && this.value !== undefined) { |
|
|
|
if (max == min) { |
|
|
|
this.radius = (this.radiusMin + this.radiusMax) / 2; |
|
|
|
} |
|
|
|
else { |
|
|
|
var scale = (this.radiusMax - this.radiusMin) / (max - min); |
|
|
|
this.radius = (this.value - min) * scale + this.radiusMin; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -413,7 +413,7 @@ Node.prototype.setValueRange = function(min, max) { |
|
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
|
*/ |
|
|
|
Node.prototype.draw = function(ctx) { |
|
|
|
throw "Draw method not initialized for node"; |
|
|
|
throw "Draw method not initialized for node"; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -422,7 +422,7 @@ Node.prototype.draw = function(ctx) { |
|
|
|
* @param {CanvasRenderingContext2D} ctx |
|
|
|
*/ |
|
|
|
Node.prototype.resize = function(ctx) { |
|
|
|
throw "Resize method not initialized for node"; |
|
|
|
throw "Resize method not initialized for node"; |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
@ -431,256 +431,256 @@ Node.prototype.resize = function(ctx) { |
|
|
|
* @return {boolean} True if location is located on node |
|
|
|
*/ |
|
|
|
Node.prototype.isOverlappingWith = function(obj) { |
|
|
|
return (this.left < obj.right && |
|
|
|
this.left + this.width > obj.left && |
|
|
|
this.top < obj.bottom && |
|
|
|
this.top + this.height > obj.top); |
|
|
|
return (this.left < obj.right && |
|
|
|
this.left + this.width > obj.left && |
|
|
|
this.top < obj.bottom && |
|
|
|
this.top + this.height > obj.top); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._resizeImage = function (ctx) { |
|
|
|
// TODO: pre calculate the image size
|
|
|
|
if (!this.width) { // undefined or 0
|
|
|
|
var width, height; |
|
|
|
if (this.value) { |
|
|
|
var scale = this.imageObj.height / this.imageObj.width; |
|
|
|
width = this.radius || this.imageObj.width; |
|
|
|
height = this.radius * scale || this.imageObj.height; |
|
|
|
} |
|
|
|
else { |
|
|
|
width = this.imageObj.width; |
|
|
|
height = this.imageObj.height; |
|
|
|
} |
|
|
|
this.width = width; |
|
|
|
this.height = height; |
|
|
|
// TODO: pre calculate the image size
|
|
|
|
if (!this.width) { // undefined or 0
|
|
|
|
var width, height; |
|
|
|
if (this.value) { |
|
|
|
var scale = this.imageObj.height / this.imageObj.width; |
|
|
|
width = this.radius || this.imageObj.width; |
|
|
|
height = this.radius * scale || this.imageObj.height; |
|
|
|
} |
|
|
|
else { |
|
|
|
width = this.imageObj.width; |
|
|
|
height = this.imageObj.height; |
|
|
|
} |
|
|
|
this.width = width; |
|
|
|
this.height = height; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawImage = function (ctx) { |
|
|
|
this._resizeImage(ctx); |
|
|
|
this._resizeImage(ctx); |
|
|
|
|
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
var yLabel; |
|
|
|
if (this.imageObj) { |
|
|
|
ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height); |
|
|
|
yLabel = this.y + this.height / 2; |
|
|
|
} |
|
|
|
else { |
|
|
|
// image still loading... just draw the label for now
|
|
|
|
yLabel = this.y; |
|
|
|
} |
|
|
|
var yLabel; |
|
|
|
if (this.imageObj) { |
|
|
|
ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height); |
|
|
|
yLabel = this.y + this.height / 2; |
|
|
|
} |
|
|
|
else { |
|
|
|
// image still loading... just draw the label for now
|
|
|
|
yLabel = this.y; |
|
|
|
} |
|
|
|
|
|
|
|
this._label(ctx, this.label, this.x, yLabel, undefined, "top"); |
|
|
|
this._label(ctx, this.label, this.x, yLabel, undefined, "top"); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Node.prototype._resizeBox = function (ctx) { |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
this.width = textSize.width + 2 * margin; |
|
|
|
this.height = textSize.height + 2 * margin; |
|
|
|
} |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
this.width = textSize.width + 2 * margin; |
|
|
|
this.height = textSize.height + 2 * margin; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawBox = function (ctx) { |
|
|
|
this._resizeBox(ctx); |
|
|
|
this._resizeBox(ctx); |
|
|
|
|
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.roundRect(this.left, this.top, this.width, this.height, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.roundRect(this.left, this.top, this.width, this.height, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Node.prototype._resizeDatabase = function (ctx) { |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
var size = textSize.width + 2 * margin; |
|
|
|
this.width = size; |
|
|
|
this.height = size; |
|
|
|
} |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
var size = textSize.width + 2 * margin; |
|
|
|
this.width = size; |
|
|
|
this.height = size; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawDatabase = function (ctx) { |
|
|
|
this._resizeDatabase(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this._resizeDatabase(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Node.prototype._resizeCircle = function (ctx) { |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
var diameter = Math.max(textSize.width, textSize.height) + 2 * margin; |
|
|
|
this.radius = diameter / 2; |
|
|
|
|
|
|
|
this.width = diameter; |
|
|
|
this.height = diameter; |
|
|
|
} |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
var diameter = Math.max(textSize.width, textSize.height) + 2 * margin; |
|
|
|
this.radius = diameter / 2; |
|
|
|
|
|
|
|
this.width = diameter; |
|
|
|
this.height = diameter; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawCircle = function (ctx) { |
|
|
|
this._resizeCircle(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this._resizeCircle(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.circle(this.x, this.y, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.circle(this.x, this.y, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._resizeEllipse = function (ctx) { |
|
|
|
if (!this.width) { |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
|
|
|
|
this.width = textSize.width * 1.5; |
|
|
|
this.height = textSize.height * 2; |
|
|
|
if (this.width < this.height) { |
|
|
|
this.width = this.height; |
|
|
|
} |
|
|
|
if (!this.width) { |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
|
|
|
|
this.width = textSize.width * 1.5; |
|
|
|
this.height = textSize.height * 2; |
|
|
|
if (this.width < this.height) { |
|
|
|
this.width = this.height; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawEllipse = function (ctx) { |
|
|
|
this._resizeEllipse(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this._resizeEllipse(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.ellipse(this.left, this.top, this.width, this.height); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.ellipse(this.left, this.top, this.width, this.height); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawDot = function (ctx) { |
|
|
|
this._drawShape(ctx, 'circle'); |
|
|
|
this._drawShape(ctx, 'circle'); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawTriangle = function (ctx) { |
|
|
|
this._drawShape(ctx, 'triangle'); |
|
|
|
this._drawShape(ctx, 'triangle'); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawTriangleDown = function (ctx) { |
|
|
|
this._drawShape(ctx, 'triangleDown'); |
|
|
|
this._drawShape(ctx, 'triangleDown'); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawSquare = function (ctx) { |
|
|
|
this._drawShape(ctx, 'square'); |
|
|
|
this._drawShape(ctx, 'square'); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawStar = function (ctx) { |
|
|
|
this._drawShape(ctx, 'star'); |
|
|
|
this._drawShape(ctx, 'star'); |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._resizeShape = function (ctx) { |
|
|
|
if (!this.width) { |
|
|
|
var size = 2 * this.radius; |
|
|
|
this.width = size; |
|
|
|
this.height = size; |
|
|
|
} |
|
|
|
if (!this.width) { |
|
|
|
var size = 2 * this.radius; |
|
|
|
this.width = size; |
|
|
|
this.height = size; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawShape = function (ctx, shape) { |
|
|
|
this._resizeShape(ctx); |
|
|
|
this._resizeShape(ctx); |
|
|
|
|
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; |
|
|
|
ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; |
|
|
|
ctx.lineWidth = this.selected ? 2.0 : 1.0; |
|
|
|
|
|
|
|
ctx[shape](this.x, this.y, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
ctx[shape](this.x, this.y, this.radius); |
|
|
|
ctx.fill(); |
|
|
|
ctx.stroke(); |
|
|
|
|
|
|
|
if (this.label) { |
|
|
|
this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top'); |
|
|
|
} |
|
|
|
if (this.label) { |
|
|
|
this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._resizeText = function (ctx) { |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
this.width = textSize.width + 2 * margin; |
|
|
|
this.height = textSize.height + 2 * margin; |
|
|
|
} |
|
|
|
if (!this.width) { |
|
|
|
var margin = 5; |
|
|
|
var textSize = this.getTextSize(ctx); |
|
|
|
this.width = textSize.width + 2 * margin; |
|
|
|
this.height = textSize.height + 2 * margin; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Node.prototype._drawText = function (ctx) { |
|
|
|
this._resizeText(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
this._resizeText(ctx); |
|
|
|
this.left = this.x - this.width / 2; |
|
|
|
this.top = this.y - this.height / 2; |
|
|
|
|
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
this._label(ctx, this.label, this.x, this.y); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Node.prototype._label = function (ctx, text, x, y, align, baseline) { |
|
|
|
if (text) { |
|
|
|
ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace; |
|
|
|
ctx.fillStyle = this.fontColor || "black"; |
|
|
|
ctx.textAlign = align || "center"; |
|
|
|
ctx.textBaseline = baseline || "middle"; |
|
|
|
|
|
|
|
var lines = text.split('\n'), |
|
|
|
lineCount = lines.length, |
|
|
|
fontSize = (this.fontSize + 4), |
|
|
|
yLine = y + (1 - lineCount) / 2 * fontSize; |
|
|
|
|
|
|
|
for (var i = 0; i < lineCount; i++) { |
|
|
|
ctx.fillText(lines[i], x, yLine); |
|
|
|
yLine += fontSize; |
|
|
|
} |
|
|
|
if (text) { |
|
|
|
ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace; |
|
|
|
ctx.fillStyle = this.fontColor || "black"; |
|
|
|
ctx.textAlign = align || "center"; |
|
|
|
ctx.textBaseline = baseline || "middle"; |
|
|
|
|
|
|
|
var lines = text.split('\n'), |
|
|
|
lineCount = lines.length, |
|
|
|
fontSize = (this.fontSize + 4), |
|
|
|
yLine = y + (1 - lineCount) / 2 * fontSize; |
|
|
|
|
|
|
|
for (var i = 0; i < lineCount; i++) { |
|
|
|
ctx.fillText(lines[i], x, yLine); |
|
|
|
yLine += fontSize; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
Node.prototype.getTextSize = function(ctx) { |
|
|
|
if (this.label != undefined) { |
|
|
|
ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace; |
|
|
|
|
|
|
|
var lines = this.label.split('\n'), |
|
|
|
height = (this.fontSize + 4) * lines.length, |
|
|
|
width = 0; |
|
|
|
if (this.label != undefined) { |
|
|
|
ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace; |
|
|
|
|
|
|
|
for (var i = 0, iMax = lines.length; i < iMax; i++) { |
|
|
|
width = Math.max(width, ctx.measureText(lines[i]).width); |
|
|
|
} |
|
|
|
var lines = this.label.split('\n'), |
|
|
|
height = (this.fontSize + 4) * lines.length, |
|
|
|
width = 0; |
|
|
|
|
|
|
|
return {"width": width, "height": height}; |
|
|
|
} |
|
|
|
else { |
|
|
|
return {"width": 0, "height": 0}; |
|
|
|
for (var i = 0, iMax = lines.length; i < iMax; i++) { |
|
|
|
width = Math.max(width, ctx.measureText(lines[i]).width); |
|
|
|
} |
|
|
|
|
|
|
|
return {"width": width, "height": height}; |
|
|
|
} |
|
|
|
else { |
|
|
|
return {"width": 0, "height": 0}; |
|
|
|
} |
|
|
|
}; |