*内容概述
---实现添加自己的线路由
---给出一个案例,线能自动绕开其他的图形.
*ManhattanConnectionRouter简介
---算法简介
与节点无关,只关心如何折线,拐点是不断取折点和结束锚点的中间点位置.
这是一个简洁的算法,却给我们提供了几点关键的概念和代码,作为设计线算法的思路.
a)直角线
开始和结束间的线不是水平线,就是垂直线,折角都是直角.
b)给出了线头的方向和线尾的方向的代码
getStartDirection(Connection conn),getEndDirection(Connection conn),getDirection(Rectangle r, Point p)
---缺点
1)线不会避开锚点间的节点,因为算法自身不考虑这个因素.
*实现智能连线
---目标
选择路径能够尽量绕开节点,效果是线不会穿过节点.
---源码
智能连线源码/******************************************************************************* * Copyright(C) 2011 Arzan Corporation. All rights reserved. * * Contributors: * Arzan Corporation - initial API and implementation *******************************************************************************/ package galaxy.ide.common.draw2d.figures; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.draw2d.AbstractRouter; import org.eclipse.draw2d.Connection; import org.eclipse.draw2d.ConnectionAnchor; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.geometry.Vector; /** * 具备自动避开节点的能力, 尽可能绕开的最短路径 * * @author dzh * @version 1.0 2011-12-16 上午10:33:38 */ public class AIConnectionRouter extends AbstractRouter { private IFigure content; private static final int DEFAULT_SPACE = 10; private PointList points; private int space; /* 离障碍物的间隙 */ private Vector endDirection; private Point endPoint; private static final Vector UP = new Vector(0, -1), DOWN = new Vector(0, 1), LEFT = new Vector(-1, 0), RIGHT = new Vector(1, 0); public AIConnectionRouter(IFigure content) { this(content, DEFAULT_SPACE); } public AIConnectionRouter(IFigure content, int space) { Assert.isNotNull(content); Assert.isLegal(space > 0, "Space must be gt zero."); this.content = content; this.space = space; this.points = new PointList(); } private boolean validConn(Connection conn) { if ((conn.getSourceAnchor() == null) || (conn.getTargetAnchor() == null)) return false; // if (conn.getSourceAnchor().getOwner() == null // || conn.getTargetAnchor().getOwner() == null) // return false; return true; } /* * (non-Javadoc) * * @see * org.eclipse.draw2d.ConnectionRouter#route(org.eclipse.draw2d.Connection) */ @Override public void route(Connection conn) { // testChildren(conn); Point startPoint = getStartPoint(conn).getCopy(); // System.out.println("startPoint: " + startPoint); conn.translateToRelative(startPoint); // System.out.println("relativeStartPoint: " + startPoint); points.addPoint(startPoint); endPoint = getEndPoint(conn).getCopy(); // System.out.println("endPoint: " + endPoint); conn.translateToRelative(endPoint); if (validConn(conn)) { Vector sdirection = getStartDirection(conn); // System.out.println("startDirection: " + direction); endDirection = getEndDirection(conn); // System.out.println("endDirection: " + endDirection); processPoints(startPoint, sdirection, null); } points.addPoint(endPoint); conn.setPoints(points.getCopy()); endDirection = null; endPoint = null; points.removeAllPoints(); } private void processPoints(Point startPoint, Vector direction, Rectangle parallelObs) { if (direction == UP) processUp(startPoint, parallelObs); else if (direction == DOWN) processDown(startPoint, parallelObs); else if (direction == LEFT) processLeft(startPoint, parallelObs); else processRight(startPoint, parallelObs); } private void processRight(Point startPoint, Rectangle parallelObs) { Point newStartPoint = new Point(startPoint); int min_xd = 0; Rectangle obstracle = null; @SuppressWarnings("unchecked") List<IFigure> list = content.getChildren(); for (IFigure f : list) { Rectangle fr = f.getBounds(); if (containPoint(fr, endPoint) || containPoint(fr, startPoint) || fr.x > endPoint.x) continue; int xd = fr.x - startPoint.x; if (xd > 0 && fr.y <= startPoint.y && (fr.y + fr.height) >= startPoint.y) { if (xd < min_xd || min_xd == 0) { min_xd = xd; obstracle = fr; } } } if (min_xd == 0) { // no obstacles if (parallelObs == null) { if (newStartPoint.y == endPoint.y) return; } else { if (newStartPoint.x < parallelObs.x) newStartPoint.x -= (parallelObs.x - newStartPoint.x) / 2; } if (newStartPoint.x < endPoint.x) { if (isVertical(endDirection, RIGHT)) newStartPoint.x = endPoint.x; else { if (endDirection.equals(RIGHT)) { newStartPoint.x = endPoint.x + space; } else newStartPoint.x += (endPoint.x - newStartPoint.x) / 2; } } } else { int x = newStartPoint.x + min_xd - space; if (x < newStartPoint.x) x = newStartPoint.x + min_xd / 2; newStartPoint.x = x; } if (parallelObs != null) { if (newStartPoint.x >= parallelObs.x && newStartPoint.x <= parallelObs.x + parallelObs.width) newStartPoint.x = parallelObs.x + parallelObs.width + space; } if (!newStartPoint.equals(startPoint)) points.addPoint(newStartPoint); // next Vector newDirection = UP; if (obstracle == null) { if (endPoint.y > newStartPoint.y) newDirection = DOWN; } else { if (endPoint.y > obstracle.y) newDirection = DOWN; } processPoints(newStartPoint, newDirection, obstracle); } private void processLeft(Point startPoint, Rectangle parallelObs) { Point newStartPoint = new Point(startPoint); int min_xd = 0; Rectangle obstracle = null; @SuppressWarnings("unchecked") List<IFigure> list = content.getChildren(); for (IFigure f : list) { Rectangle fr = f.getBounds(); if (containPoint(fr, endPoint) || containPoint(fr, startPoint) || (fr.x + fr.width) <= endPoint.x) continue; int xd = startPoint.x - fr.x - fr.width; if (xd > 0 && fr.y <= startPoint.y && (fr.y + fr.height) >= startPoint.y) { if (xd < min_xd || min_xd == 0) { min_xd = xd; obstracle = fr; } } } if (min_xd == 0) { // no obstacles // not need bend point if (parallelObs == null) { if (newStartPoint.y == endPoint.y) return; } else { if (newStartPoint.x > parallelObs.x + parallelObs.width) newStartPoint.x -= (newStartPoint.x - parallelObs.x - parallelObs.width) / 2; } if (newStartPoint.x > endPoint.x) { if (isVertical(endDirection, LEFT)) newStartPoint.x = endPoint.x; else { if (endDirection.equals(LEFT)) { newStartPoint.x = endPoint.x - space; } else newStartPoint.x += (newStartPoint.x - endPoint.x) / 2; } } } else { int x = newStartPoint.x + min_xd - space; if (x < newStartPoint.x) x = newStartPoint.x + min_xd / 2; newStartPoint.x = x; } if (parallelObs != null) { if (newStartPoint.x >= parallelObs.x && newStartPoint.x <= (parallelObs.x + parallelObs.width)) { newStartPoint.x = parallelObs.x - space; } } if (!newStartPoint.equals(startPoint)) points.addPoint(newStartPoint); // next row point Vector newDirection = UP; if (obstracle == null) { if (endPoint.y > newStartPoint.y) newDirection = DOWN; } else { if (endPoint.y >= obstracle.y) newDirection = DOWN; } processPoints(newStartPoint, newDirection, obstracle); } private void processDown(Point startPoint, Rectangle parallelObs) { Point newStartPoint = new Point(startPoint); int min_yd = 0; Rectangle obstracle = null; @SuppressWarnings("unchecked") List<IFigure> list = content.getChildren(); for (IFigure f : list) { Rectangle fr = f.getBounds(); // System.out.println(fr.width); if (containPoint(fr, endPoint) || containPoint(fr, startPoint) || fr.y > endPoint.y) continue; int yd = fr.y - startPoint.y; if (yd > 0 && fr.x <= startPoint.x && (fr.x + fr.width) >= startPoint.x) { if (yd < min_yd || min_yd == 0) { min_yd = yd; obstracle = fr; } } } if (min_yd == 0) { // no obstacles // not need bend point if (parallelObs == null) { if (newStartPoint.x == endPoint.x) return; } else { if (parallelObs.y > startPoint.y) newStartPoint.y += (parallelObs.y - startPoint.y) / 2; } if (newStartPoint.y < endPoint.y) { if (isVertical(endDirection, DOWN)) { newStartPoint.y = endPoint.y; // TODO avoid itself } else { if (endDirection.equals(DOWN)) newStartPoint.y = startPoint.y + space; else newStartPoint.y += (endPoint.y - newStartPoint.y) / 2; } } } else { int y = newStartPoint.y + min_yd - space; if (y < newStartPoint.y) y = newStartPoint.y + min_yd / 2; newStartPoint.y = y; } if (parallelObs != null) { if (newStartPoint.y > parallelObs.y && newStartPoint.y < parallelObs.y + parallelObs.height) newStartPoint.y = parallelObs.y + parallelObs.height + space; } if (!newStartPoint.equals(startPoint)) points.addPoint(newStartPoint); // System.out.println("point:" + newStartPoint.toString()); // next row point Vector newDirection = LEFT; if (obstracle == null) { if (endPoint.x > newStartPoint.x) newDirection = RIGHT; } else { if (endPoint.x > (obstracle.x + obstracle.width)) newDirection = RIGHT; } processPoints(newStartPoint, newDirection, obstracle); } boolean isVertical(Vector v1, Vector v2) { double val = v1.x * v2.x + v1.y * v2.y; if (val == 0) return true; return false; } boolean containPoint(Rectangle r, Point p) { return p.x >= r.x && p.x <= r.x + r.width && p.y >= r.y && p.y <= r.y + r.height; } private void processUp(Point startPoint, Rectangle parallelObs) { Point newStartPoint = new Point(startPoint); int min_yd = 0; Rectangle obstracle = null; @SuppressWarnings("unchecked") List<IFigure> list = content.getChildren(); for (IFigure f : list) { Rectangle fr = f.getBounds(); if (containPoint(fr, endPoint) || containPoint(fr, startPoint) || (fr.y + fr.height) <= endPoint.y) continue; int yd = startPoint.y - fr.y - fr.height; if (yd > 0 && fr.x <= startPoint.x && (fr.x + fr.width) >= startPoint.x) { if (yd < min_yd || min_yd == 0) { min_yd = yd; obstracle = fr; } } } if (min_yd == 0) { // no obstacles // not need bend point if (parallelObs == null) { if (newStartPoint.x == endPoint.x) return; } else { if (newStartPoint.y > parallelObs.y + parallelObs.height) newStartPoint.y -= (newStartPoint.y - parallelObs.y - parallelObs.height) / 2; } if (newStartPoint.y > endPoint.y) { if (isVertical(endDirection, UP)) newStartPoint.y = endPoint.y; else { if (endDirection.equals(UP)) { newStartPoint.y = endPoint.y - space; } else newStartPoint.y -= (newStartPoint.y - endPoint.y) / 2; } } } else { int y = newStartPoint.y - min_yd + space; if (y > newStartPoint.y) y = newStartPoint.y - min_yd / 2; newStartPoint.y = y; } if (parallelObs != null) { if (newStartPoint.y >= parallelObs.y && newStartPoint.y <= parallelObs.y + parallelObs.height) newStartPoint.y = parallelObs.y - space; } if (!newStartPoint.equals(startPoint)) points.addPoint(newStartPoint); // next row point Vector newDirection = LEFT; if (obstracle == null) { if (endPoint.x > newStartPoint.x) newDirection = RIGHT; } else { if (endPoint.x >= obstracle.x) newDirection = RIGHT; } processPoints(newStartPoint, newDirection, obstracle); } protected Vector getDirection(Rectangle r, Point p) { int i, distance = Math.abs(r.x - p.x); Vector direction; direction = LEFT; i = Math.abs(r.y - p.y); if (i <= distance) { distance = i; direction = UP; } i = Math.abs(r.bottom() - p.y); if (i <= distance) { distance = i; direction = DOWN; } i = Math.abs(r.right() - p.x); if (i < distance) { distance = i; direction = RIGHT; } return direction; } protected Vector getStartDirection(Connection conn) { ConnectionAnchor anchor = conn.getSourceAnchor(); Point p = getStartPoint(conn); Rectangle rect; if (anchor.getOwner() == null) rect = new Rectangle(p.x - 1, p.y - 1, 2, 2); else { rect = conn.getSourceAnchor().getOwner().getBounds().getCopy(); conn.getSourceAnchor().getOwner().translateToAbsolute(rect); } return getDirection(rect, p); } protected Vector getEndDirection(Connection conn) { ConnectionAnchor anchor = conn.getTargetAnchor(); Point p = getEndPoint(conn); Rectangle rect; if (anchor.getOwner() == null) rect = new Rectangle(p.x - 1, p.y - 1, 2, 2); else { rect = conn.getTargetAnchor().getOwner().getBounds().getCopy(); conn.getTargetAnchor().getOwner().translateToAbsolute(rect); } return getDirection(rect, p); } // private IFigure getSourceOwner(Connection conn) { // return conn.getSourceAnchor().getOwner(); // } // // private IFigure getTargetOwner(Connection conn) { // return conn.getTargetAnchor().getOwner(); // } // private void testChildren(Connection conn) { // List<Figure> list = content.getChildren(); // for (Figure f : list) { // Rectangle r = f.getBounds(); // // conn.translateToRelative(r.getCopy()); // Rectangle rc = r.getCopy(); // content.translateToRelative(rc.getCopy()); // System.out.println(rc.toString()); // // if(f instanceof HandleBounds){ // // // // } // } // System.out.println("\n"); // } }
---源码说明
这里只介绍思路,和关键点
a)实现线路由的基本方式,
继承org.eclipse.draw2d.AbstractRouter-->自定义route()方法,它是布局算法的入口,算法就在这个方法里实现
-->getStartPoint(Connection conn)获取起点+getEndPoint(Connection connection)获取终点+自定义算法获取的其他点,
所有的这些点全部放入PointList中-->route()的最后关键就是conn.setPoints(points.getCopy());这个方法将会重新绘制连线.
b)这里添加的private IFigure content;就是要避开节点的父容器图形;
c)要注意,conn.translateToRelative(startPoint);
这个方法的作用,把点换算成相对线容器的相对坐标,比如说GEF对figure做了放大缩小后,调用这个方法才能保证节点坐标的相对正确性.
d)解决方法,一个基本的障碍物模型+递推+直角线的方式,读者只需看其中一个方向的处理代码就能明白细节思路了.
下图是processDown时的障碍物模型图
e)后续优化点,在考虑障碍物时,没有把开始和结束点所在的figure作为障碍物,这样就导致了线穿过开始或者结束figure的情况.
*添加路由
一般有2中方式给线添加路由
---统一添加方式
在figure的连接层添加,那么这个线路由将作用于其中的所有线
全局添加ConnectionLayer connLayer = (ConnectionLayer) ((CommonNodeEditPart)editPart).getLayer(org.eclipse.gef.LayerConstants.CONNECTION_LAYER); connLayer.setConnectionRouter(new AIConnectionRouter(content));
---个别添加,直接调用线自身的方法
void org.eclipse.draw2d.PolylineConnection.setConnectionRouter(ConnectionRouter cr)
*总结
---draw2d已提供ManhattanConnectionRouter,BendpointConnectionRouter,ShortestPathConnectionRouter等,可以直接使用或者继承后做些修改.
---当需要自己定制连线路由时,继承AbstractRouter,并自定义route(Connection conn)方法,也不难,麻烦的可能就是算法.
---线的路由算法,设计思路是建立解决方案的基本模型,保证通用正确性处理,兼顾考虑特殊情况并给出特殊优化,这也是算法设计的基本思路之一.