- The link is divided into two parts with two colors to fill.
- Use different colors to fill different percentages.
- Show scales.
- Have a switch to control the length of the link. After shortening the link, there are only the beginning part and the ending part (equivalent to a episode of the original link) with no middle part.
- If there are more than one links, the parts of the highest percentage will be shown at the two sides of the link after combining. Before: After:
- After entering the subnetwork, the information on the link between the node in the subnetwork and the node in the upper layer will show in the subnetwork.
After entering the subnetwork, the link will also show beside node 4.
Let’s first see the effect. Then we will explain how to customize the link after that.
The first 5 requirements can be met by customizing Link and LinkUI. Notice that:
- We obtain all bundled links through Link#getBundleLinks.
- We draw lines through LinkUI#drawLinePoints.
//customize the constructor function of LinkThe last requirement can be met by customizing Node and NodeUI.
demo.ScaleLink = function(id, from, to) {
//call the constructor function of the base class
demo.ScaleLink.superClass.constructor.call(this, id, from, to);
//Set the width of the Link as 10 pixes
this.setStyle('link.width', 10);
this.setStyle('link.color', 'rgba(0, 0, 0, 0)');
//Set the type of Link as parallel.
this.setStyle('link.type', 'parallel');
//Set the distance between every two links in a bundled link as 40.
this.setStyle('link.bundle.offset', 40);
//Set the color of the scales.
this.setClient('scaleColor', 'black');
//Set the width of the scales.
this.setClient('scaleWidth', 1);
//Set the number the scales.
this.setClient('scaleNumbers', 4);
//Set whether the Link is shortened or not.
this.setClient('shortened', false);
//Set the length of the link after it is shortened.
this.setClient('shortenLength', 100);
//Set the color of the splitter.
this.setClient('splitterColor', 'black');
// Set the percentage in the first part of the Link
this.setClient('fromFillPercent', 0);
//Set the percentage in the last part of the Link
this.setClient('toFillPercent', 0);
};
//Set customized Link to inherit from twaver.Link
twaver.Util.ext('demo.ScaleLink', twaver.Link, {
//reload the method to obtain UI class; returns the customized UI class
getCanvasUIClass : function () {
return demo.ScaleLinkUI;
},
//obtain the filling color according to the percentage
getFillColor: function(percent) {
if (percent < 0.25) {
return 'green';
}
if (percent < 0.5) {
return 'yellow';
}
if (percent < 0.75) {
return 'magenta';
}
return 'red';
},
// obtain the filling color in the first part
getFromFillColor: function () {
return this.getFillColor(this.getFromFillPercent());
},
// obtain the filling color in the last part of the Link
getToFillColor: function () {
return this.getFillColor(this.getToFillPercent());
},
// obtain the percentage in the first part of the Link
getFromFillPercent: function () {
// If there is the bundled link agent between two nodes, the value of the biggest filling percentage in all links in the bundled link returns.
if (this.isBundleAgent()) {
var fromAgent = this.getFromAgent(),
percentKey, maxPercent = 0, percent;
this.getBundleLinks().forEachSiblingLink(function (link) {
percentKey = fromAgent === link.getFromAgent() ? 'fromFillPercent' : 'toFillPercent';
percent = link.getClient(percentKey);
maxPercent = percent > maxPercent ? percent : maxPercent;
});
return maxPercent;
} else {
return this.getClient('fromFillPercent');
}
},
// obtain the percentage in the last part of the Link
getToFillPercent: function () {
// If there is the bundled link agent between two nodes, the value of the biggest filling percentage in all links in the bundled link returns.
if (this.isBundleAgent()) {
var toAgent = this.getToAgent(),
percentKey, maxPercent = 0, percent;
this.getBundleLinks().forEachSiblingLink(function (link) {
percentKey = toAgent === link.getToAgent() ? 'toFillPercent' : 'fromFillPercent';
percent = link.getClient(percentKey);
maxPercent = percent > maxPercent ? percent : maxPercent;
});
return maxPercent;
} else {
return this.getClient('toFillPercent');
}
},
// reload the method to obtain the element name; if there is the bundled link agent between two nodes, the names of the first and the last agent node will return.
getName: function () {
if (this.getClient('shortened')) {
return null;
} else if (this.isBundleAgent()) {
return this.getFromAgent().getName() + '-' + this.getToAgent().getName();
} else {
return demo.ScaleLink.superClass.getName.call(this);
}
}
});
// customize constructor function of LinkUI
demo.ScaleLinkUI = function(network, element){
// call the constructor function of the type class
demo.ScaleLinkUI.superClass.constructor.call(this, network, element);
};
// customize Link to inherit twaver.canvas.LinkUI
twaver.Util.ext('demo.ScaleLinkUI', twaver.canvas.LinkUI, {
// obtain the angle of the Link
getAngle: function () {
return getAngle(this.getFromPoint(), this.getToPoint());
},
// obtain the central point of the Link
getMiddlePoint: function (from, to, percent) {
return {
x: from.x + (to.x - from.x) * percent,
y: from.y + (to.y - from.y) * percent
};
},
// draw scale lines
drawScaleLine: function (from, to, angle, length, ctx, percent, lineWidth, lineColor) {
var point = this.getMiddlePoint(from, to, percent);
var y = length/2 * Math.sin(angle),
x = length/2 * Math.cos(angle);
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = lineColor;
ctx.moveTo(point.x + x, point.y + y);
ctx.lineTo(point.x - x, point.y -y);
ctx.stroke();
},
// obtain whether the link is shortened or not
isShorten: function () {
var link = this.getElement();
return link.getClient('shortened') && this.getLineLength() > link.getClient('shortenLength') * 2;
},
// reload the function to draw the link and to draw it by customized logic
paintBody: function (ctx) {
var points = this.getLinkPoints(),
link = this.getElement();
if (!points || points.size() < 2) {
return;
}
var lineLength = this.getLineLength(),
shortenLength = link.getClient('shortenLength'),
percent = shortenLength / lineLength,
from = points.get(0),
to = points.get(1),
angle = this.getAngle() + Math.PI/2;
if (this.isShorten()) {
fromPoints = new twaver.List([from, this.getMiddlePoint(from, to, percent)]);
toPoints = new twaver.List([this.getMiddlePoint(from, to, 1 - percent), to]);
this._paintBody(ctx, fromPoints, angle);
this._paintBody(ctx, toPoints, angle);
// draw texts
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'black';
var textCenter = {x: (fromPoints.get(0).x + fromPoints.get(1).x)/2, y: (fromPoints.get(0).y + fromPoints.get(1).y)/2};
ctx.fillText(link.getName(), textCenter.x, textCenter.y);
textCenter = {x: (toPoints.get(0).x + toPoints.get(1).x)/2, y: (toPoints.get(0).y + toPoints.get(1).y)/2};
ctx.fillText(link.getName(), textCenter.x, textCenter.y);
ctx.fillText(link.getToNode().getName(), fromPoints.get(1).x, fromPoints.get(1).y);
ctx.fillText(link.getFromNode().getName(), toPoints.get(0).x, toPoints.get(0).y);
} else {
this._paintBody(ctx, points, angle);
}
// draw the first arrow
if (link.getClient('arrow.from')) {
twaver.Util.drawArrow(ctx, 12, 9, points, true, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
}
// draw the last arrow
if (link.getClient('arrow.to')) {
twaver.Util.drawArrow(ctx, 12, 9, points, false, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
}
},
_paintBody: function (ctx, points, angle) {
var link = this.getElement(),
width = link.getStyle('link.width'),
grow = width,
outerColor = this.getOuterColor();
if (outerColor) {
var outerWidth = link.getStyle('outer.width');
grow += outerWidth * 2;
}
var selectBorder = !this.getEditAttachment() && link.getStyle('select.style') === 'border' && this.getNetwork().isSelected(link);
if (selectBorder) {
var selectWidth = link.getStyle('select.width');
grow += selectWidth * 2;
}
ctx.lineCap = link.getStyle('link.cap');
ctx.lineJoin = link.getStyle('link.join');
// draw the selected outline
if (selectBorder) {
this.drawLinePoints(ctx, points, grow, link.getStyle('select.color'));
}
// draw the outline
if (outerColor) {
this.drawLinePoints(ctx, points, width + outerWidth * 2, outerColor);
}
// draw the Link
this.drawLinePoints(ctx, points, width, this.getInnerColor() || link.getStyle('link.color'));
var fromFillPercent = link.getFromFillPercent(),
toFillPercent = link.getToFillPercent(),
fromFillColor = link.getFromFillColor(),
toFillColor = link.getToFillColor(),
from = points.get(0),
to = points.get(1);
var x = from.x + (to.x - from.x) / 2 * fromFillPercent,
y = from.y + (to.y - from.y) / 2 * fromFillPercent;
var middle = {x: x, y: y};
var fromPoints = new twaver.List([from, middle]);
// draw filling color of the first part
this.drawLinePoints(ctx, fromPoints, width, fromFillColor);
from = points.get(1);
to = points.get(0);
x = from.x + (to.x - from.x) / 2 * toFillPercent;
y = from.y + (to.y - from.y) / 2 * toFillPercent;
middle = {x: x, y: y};
var toPoints = new twaver.List([from, middle]);
// draw filling color of the last part
this.drawLinePoints(ctx, toPoints, width, toFillColor);
from = points.get(0);
to = points.get(1);
var scaleWidth = link.getClient('scaleWidth'),
scaleColor = link.getClient('scaleColor');
// draw the scales
for (var i = 1, n = link.getClient('scaleNumbers') * 2; i < n; i++) {
this.drawScaleLine(from, to, angle, width/2, ctx, i/n, scaleWidth, scaleColor);
}
// draw the spiltter
this.drawScaleLine(from, to, angle, width, ctx, 0.5, 3, link.getClient('splitterColor'));
}
});
// customize the constructor function of Node
demo.ScaleNode = function(id) {
// call the constructor function of the base class
demo.ScaleNode.superClass.constructor.call(this, id);
};
// set the customized Node to inherit from twaver.Nodes
twaver.Util.ext('demo.ScaleNode', twaver.Node, {
getCanvasUIClass: function () {
return demo.ScaleNodeUI;
}
});
// customize the constructor function of NodeUI
demo.ScaleNodeUI = function(network, element){
// call the constructor function of the base class
demo.ScaleNodeUI.superClass.constructor.call(this, network, element);
};
// set the customized NodeUI to inherit from twaver.canvas.NodeUI
twaver.Util.ext('demo.ScaleNodeUI', twaver.canvas.NodeUI, {
// reload the method to draw the element and draw the link to the upper layer
paintBody: function (ctx) {
demo.ScaleNodeUI.superClass.paintBody.call(this, ctx);
var result = this.getAttachedLinks();
if (!result) {
return;
}
for (var position in result) {
this.paintLink(ctx, result[position], position);
}
},
// draw the link
paintLink: function (ctx, links, position) {
var center = this.getElement().getCenterLocation(),
count = links.length,
half = count / 2,
network = this.getNetwork(),
gap = (count - 1) * -10,
terminal, link, i, offset, shortenLength, angle, tempCenter, textWidth, textHeight = 20, textCenter;
for (i=0; i<count; i++) {
link = links[i];
offset = link.getStyle('link.bundle.offset');
shortenLength = link.getClient('shortenLength');
textWidth = ctx.measureText(link.getName()).width;
if (position === 'left') {
terminal = {x: center.x - offset - shortenLength, y: center.y + gap};
tempCenter = {x: center.x - offset, y: center.y + gap};
textCenter = {x: terminal.x - textWidth/2 - 10, y: terminal.y};
angle = Math.PI/2;
} else if (position === 'right') {
terminal = {x: center.x + offset + shortenLength, y: center.y + gap};
tempCenter = {x: center.x + offset, y: center.y + gap};
textCenter = {x: terminal.x + textWidth/2 + 10, y: terminal.y};
angle = Math.PI/2;
} else if (position === 'top') {
terminal = {x: center.x + gap, y: center.y - offset - shortenLength};
tempCenter = {x: center.x + gap, y: center.y - offset};
textCenter = {x: terminal.x, y: terminal.y - 10};
angle = 0;
} else {
terminal = {x: center.x + gap, y: center.y + offset + shortenLength};
tempCenter = {x: center.x + gap, y: center.y + offset};
textCenter = {x: terminal.x, y: terminal.y + 10};
angle = 0;
}
gap += 20;
var isFrom = link.getFromNode() === this.getElement(),
points;
if (isFrom) {
points = new twaver.List([tempCenter, terminal]);
} else {
points = new twaver.List([terminal, tempCenter]);
}
network.getElementUI(link)._paintBody(ctx, points, angle);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'black';
// the label of the node at another side
var name = isFrom ? link.getToNode().getName() : link.getFromNode().getName();
ctx.fillText(name, textCenter.x, textCenter.y);
textCenter = {x: (tempCenter.x + terminal.x)/2, y: (tempCenter.y + terminal.y)/2};
// the label of Link
ctx.fillText(link.getName(), textCenter.x, textCenter.y);
// draw the first arrow
if (link.getClient('arrow.from')) {
twaver.Util.drawArrow(ctx, 12, 9, points, true, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
}
// draw the last arrow
if (link.getClient('arrow.to')) {
twaver.Util.drawArrow(ctx, 12, 9, points, false, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
}
}
},
// obtain the collection of the links to the upper layer of different directions
getAttachedLinks: function () {
var currentSubNetwork = this.getNetwork().getCurrentSubNetwork();
if (!currentSubNetwork || !this.getElement().getLinks()) {
return null;
}
var result;
this.getElement().getLinks().forEach(function (link) {
var fromSubNetwork = twaver.Util.getSubNetwork(link.getFromNode()),
toSubNetwork = twaver.Util.getSubNetwork(link.getToNode());
if (fromSubNetwork !== toSubNetwork) {
if (!result) {
result = {};
}
var fromCenter = link.getFromNode().getCenterLocation(),
toCenter = link.getToNode().getCenterLocation(),
angle = getAngle(fromCenter, toCenter),
isOut = currentSubNetwork === fromSubNetwork,
position;
if (isOut) {
if (fromCenter.x <= toCenter.x) {
if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
position = 'right';
} else if (angle > Math.PI/4) {
position = 'bottom';
} else {
position = 'top';
}
} else {
if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
position = 'left';
} else if (angle > Math.PI/4) {
position = 'top';
} else {
position = 'bottom';
}
}
} else {
if (fromCenter.x <= toCenter.x) {
if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
position = 'left';
} else if (angle > Math.PI/4) {
position = 'top';
} else {
position = 'bottom';
}
} else {
if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
position = 'right';
} else if (angle > Math.PI/4) {
position = 'bottom';
} else {
position = 'top';
}
}
}
if (!result[position]) {
result[position] = [];
}
result[position].push(link);
}
});
return result;
}
});
No comments:
Post a Comment