前端开发框架总结之利用Jtopo实现网络拓扑功能(二)
上文我们讲了一些拓扑结点生成的实际场景设计和实现思路以及一些关键技术细节。本文我们继续我们的拓扑管理开发之旅。
-
拓扑连线
给拓扑结点添加连线是拓扑管理中必不可少的操作,官网的例子中也有相应的例子。但是如果我们完全按照这个例子的操作方式用到我们项目中的话,其实是很不可取的,例子中是单击结点进行连线,但是实际操作过程中很多时候单击只是为了选中结点而已,然后进行结点的拖拽,拉伸等操作。另外单击操作也会和双击等操作混在一起,导致连线操作交互体验很差。本人在这里推荐一种交互方式供大家参考:我们可以把双击结点的操作定义为连线的触发动作,把双击后对结点的单击动作作为连线的结束动作,经实际操作体验是很不错的。
官方提供的API中只有生成拓扑连线的方法。我们想要实现在连线过程中线的终点跟随鼠标移动效果,其实就是不断的设置连线的一个Node的position。这个场景的实现过程中有三个关键点,1、记录连线起点变量的控制。2、虚拟连线的控制,包括终点位置的设置和虚拟连线的删除等。(为了视觉体验优化,可以设置此虚拟连线为虚线)。3、scene的mousemove事件的监听。
js代码片段
function nodeDbClick(e){
console.log("nodeDbClick");
beginNode = e.target;
tempNodeS.setLocation(beginNode.x + beginNode.width*0.5, beginNode.y + beginNode.height*0.5);
tempNodeE.setLocation(e.x, e.y);
scene.add(tempLink);
}
function nodeClick(e){
console.log("nodeClick");
if(beginNode){
if(beginNode != e.target){
var link = new JTopo.Link(beginNode, e.target);
link.strokeColor = '181,190,196';
scene.add(link);
link.addEventListener('mouseup',linkMouseUp);
beginNode = null;
scene.remove(tempLink);
}
else{
beginNode = null;
scene.remove(tempLink);
}
}
else{
scene.remove(tempLink);
}
}
function sceneMouseUp(e){
console.log("sceneMouseUp");
if(e.target){
if(e.target instanceof JTopo.Node){
if(e.target instanceof JTopo.TextNode){
scene.remove(tempLink);
beginNode = null;
}
}
else{
scene.remove(tempLink);
beginNode = null;
}
if(nodeSelected){
if(nodeSelected != e.target){
nodeSelected = undefined;
var elem = document.getElementById('menu');
elem.style.visibility = 'hidden';
}
}
if(linkSelected){
if(linkSelected != e.target){
linkSelected = undefined;
var elem = document.getElementById('linkMenu');
elem.style.visibility = 'hidden';
}
}
}
else{
scene.remove(tempLink);
beginNode = null;
//右键画布会隐藏菜单。和画布的click事件配合使用。
if(e.button == 2){
nodeSelected = undefined;
var elem = document.getElementById('menu');
elem.style.visibility = 'hidden';
linkSelected = undefined;
var elem = document.getElementById('linkMenu');
elem.style.visibility = 'hidden';
}
}
}
function sceneMouseMove(e){
//如果连线已经确认了起点,证明已经开始划线,则终点需要随鼠标移动。
if(beginNode){
tempNodeE.setLocation(e.x, e.y);
}
}
- 拓扑文本
拓扑文本对象可以作为拓扑管理中的一个辅助元素,用以在拓扑图中插入一段文字。拓扑文本对象是TextNode类型。注意如果使用scene的findAPI查找node类型的结点是不包括textNode类型的结点的。此功能我是把他作为拓扑工具栏出的一个快捷按钮来设计的。实现此功能会设计到两个关键点:1、文本结点的location设置为多少合适。2、文本节点的内容如何修改。
首先,文本节点的位置这个问题好处理,只不过我们实际操作中需要注意由于scene的拖拽和缩放等造成的textnode移位问题。tips:在拓扑操作过程中经常会遇到一些由于画布拖拽和缩放等引起的位置不准确问题,这个我们只要了解了JTopo的拖拽和缩放的实现基本就能很好的解决遇到的各种位置问题。拖拽对应的是scene的translateX、translateY属性,就是x、y轴方向上的移动。缩放对应的是scene的scaleX、scaleY属性,只不过缩放操作是以当前视野中的区域的中心为缩放参考点,以拓扑元素和这个参考点的连线为轴线进行缩放操作的。也就是说缩放时,拓扑元素的运动轨迹永远是在这个轴线上的。另外还需考虑拓扑结点本身的size属性受缩放的影响。
其次,文本节点内容的修改方式,可以参考官方示例的操作逻辑把输入界面美化就可以了。这个功能的难点同样是文本输入界面的位置的计算,需要加入缩放、移位等因素的考虑。另外还有一个小的细节是记得不要把textnode的text设置为空,否则文本节点的宽度为0,你就无法设计结点的删除操作了。
js代码
$scope.addTopoText = function () {
var center = getCenterXY();
var textNode = new JTopo.TextNode('双击编辑文本');
debugger;
/*textNode.height = 50;
textNode.width = 220;
textNode.borderColor = '0,255,0';
textNode.borderWidth = 1;*/
//textNode.setBound(0,0);
textNode.fontColor = '0,0,0';
textNode.font = 'bold 14px Consolas';
textNode.textPosition = 'Middle_Center';
/*
textNode.fontColor = '255,0,0';
textNode.borderRadius = 20; // 圆角
textNode.fillColor = '255,255,255';
textNode.alpha = 1;*/
scene.add(textNode);
console.log("textNode,width:" + textNode.width + ",height:" + textNode.height);
/**由于文本节点未在界面绘制,所以目前是无法获取节点的长和宽的,但是我们生成的结点初始内容是固定的,
* 经过测试width:96,height:12,后续字体有调整的话,需要根据实际情况修改一下。
**/
textNode.setLocation(center.x - 48,center.y - 6);
textNode.virtualId = new Date().getTime();
textNode.addEventListener('mousedrag',nodeMouseDrag);
textNode.addEventListener('dbclick',textDbClick);
textNode.addEventListener('mouseup',linkMouseUp);
}
function textDbClick(event) {
var e = event.target;
var elem = angular.element('#textNodeName');
elem = document.getElementById("textNodeName");
elem.value = e.text;
e.text = "";
elem.JTopoNode = e;
console.log("event,X:" + event.x + ",y:" + event.y);
console.log("event,tX:" + event.tx + ",ty:" + event.ty);
console.log("event,pageX:" + event.pageX + ",pageY:" + event.pageY);
console.log("event,offsetX:" + event.offsetX + ",offsetY:" + event.offsetY);
console.log("event,clientX:" + event.clientX + ",clientY:" + event.clientY);
console.log("event,screenX:" + event.screenX + ",screenY:" + event.screenY);
console.log("e.x:" + ( e.x + scene.translateX) + ",e.y:" + ( e.y + scene.translateY) );
//$scope.textNodeStyle.top = event.offsetY + yOffset - 0;
//$scope.textNodeStyle.left = event.offsetX + xOffset - 0;
$scope.textNodeStyle.top = e.y + scene.translateY + yOffset - 5;
$scope.textNodeStyle.left = e.x + scene.translateX + xOffset - 5;
$scope.textNodeStyle.visibility = "visible";
$scope.$apply();
$timeout(function () {
elem.focus();
});
}
$scope.setTextNodeName = function (event) {
if(event.target.value.length > 0){
event.target.JTopoNode.text = event.target.value;
}
else{
event.target.JTopoNode.text = '双击编辑文本';
}
$scope.textNodeStyle.visibility = "hidden";
$scope.$apply();
}