想想Java2D中給我們提供的線的樣式著實(shí)很少,除了直線,虛線,好像就沒有其他的什么樣式了,如果細(xì)心的童鞋還會(huì)發(fā)現(xiàn),TWaver中倒是提供了一種比較特殊的連線,波浪曲折式的連線。

這種波浪曲折的連線如果讓我們自己來實(shí)現(xiàn)也是有多種實(shí)現(xiàn)的方式,還記得之前幾篇文章中定制過的LinkUI么,也是各式各樣的方式,比如:
五彩斑斕的Link
流動(dòng)點(diǎn)式的Link
今天給大家介紹的是箭頭流動(dòng)式的Link,何為箭頭流動(dòng),我們就先來看看效果圖:
這是一個(gè)從from節(jié)點(diǎn)流向to節(jié)點(diǎn)的連線,連線是以一個(gè)一個(gè)箭頭組建而成,這樣的連線方式看上去比傳統(tǒng)的那種流動(dòng)漂亮多了,也有不少客戶提及到這種樣式。本篇我將詳細(xì)給大家講解一下實(shí)現(xiàn)的細(xì)節(jié)。
首先需要定制一個(gè)ArrowLink繼承于Link,在ArrowLink中需要給它定義幾個(gè)變量,例如:線的寬度、顏色、每段箭頭的長(zhǎng)度、需要填充的流動(dòng)箭頭的數(shù)量、透明度、是否是從from流向to以及偏移(用于顯示流動(dòng))等等。 不說這么多了,直接看代碼:
1 public class ArrowLink extends Link {
2
3 public ArrowLink() {
4 super();
5 init();
6 }
7
8 public ArrowLink(Object id) {
9 super(id);
10 init();
11 }
12
13 public ArrowLink(Node from, Node to) {
14 super(from, to);
15 init();
16 }
17
18 public ArrowLink(Object id, Node from, Node to) {
19 super(id, from, to);
20 init();
21 }
22
23 private void init() {
24 this.putLinkColor(new Color(0, 0, 0, 0));
25 this.putLinkOutlineWidth(0);
26 this.setLinkType(TWaverConst.LINK_TYPE_PARALLEL);
27 this.putLinkAntialias(true);
28 this.putClientProperty("lineWidth", 3.0f);
29 this.putClientProperty("lineColor", Color.blue);
30 this.putClientProperty("offset", 0.0);
31 this.putClientProperty("segmentLength", 8.0);
32 this.putClientProperty("fillSegmentCount", 5);
33 this.putClientProperty("defaultAlpha", 0.2);
34 this.putClientProperty("from", true);
35 }
36
37 public String getUIClassID() {
38 return ArrowLinkUI.class.getName();
39 }
40
41 }
定制完連線之后,最主要的是需要重畫LinkUI,自定義ArrowLinkUI類繼承于LinkUI并重載paintBody方法,paintBody中我們需要畫出一個(gè)一個(gè)的箭頭,箭頭實(shí)現(xiàn)起來其實(shí)還是比較簡(jiǎn)單的,我們能獲取Link的長(zhǎng)度并且知道Link上每段的長(zhǎng)度,就可以計(jì)算出需要繪制箭頭的數(shù)量。
1 int count = (int)(length/segmentLength);
根據(jù)箭頭的數(shù)量可以獲取到需要繪制箭頭的每個(gè)點(diǎn)的位置:
1 List points = TWaverUtil.divideShape(this.path, count);
獲取到這個(gè)位置之后,我們就可以以這個(gè)點(diǎn)為中心點(diǎn),分別計(jì)算出箭頭的其他兩個(gè)點(diǎn)的位置:
1 Point2D p0 = new Point.Double();
2 transform.transform(point, p0);
3 Point2D p1 = new Point.Double();
4 transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() - segmentLength/2), p1);
5 Point2D p2 = new Point.Double();
6 transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() + segmentLength/2), p2);
這樣一個(gè)箭頭就可以繪制出來了
其他的箭頭也可以以同樣方式循環(huán)繪制出來,需要注意的是箭頭是需要隨著node的位置旋轉(zhuǎn)的,因此我們需要計(jì)算出箭頭旋轉(zhuǎn)的角度和旋轉(zhuǎn)點(diǎn)的位置:
1 AffineTransform transform = new AffineTransform();
2 transform.translate(point.getX(), point.getY());
3 transform.rotate(angle);
4 transform.translate(-point.getX(), -point.getY());
最后還有流動(dòng)的效果,這里我們?cè)O(shè)置了一個(gè)offset的參數(shù),可以表示流動(dòng)的偏移量,根據(jù)偏移量以及填充的流動(dòng)箭頭的數(shù)量來確定當(dāng)前這個(gè)箭頭的透明度:
1 double alpha = (Double)this.element.getClientProperty("defaultAlpha");
2 if(offset * count >= i && offset * count - fillSegmentCount <= i){
3 alpha = 1 - (offset * count - i)/fillSegmentCount * 0.5;
4 }
完整繪制箭頭的代碼如下:
1 public class ArrowLinkUI extends LinkUI {
2
3 public ArrowLinkUI(TNetwork network, Link link) {
4 super(network, link);
5 }
6
7 public void paintBody(Graphics2D g2d) {
8 super.paintBody(g2d);
9 this.drawFlowing(g2d);
10 }
11
12 private void drawFlowing(Graphics2D g2d) {
13 double length = TWaverUtil.getLength(this.path);
14 if(length < =0 ){
15 return;
16 }
17
18 double segmentLength = (Double)this.element.getClientProperty("segmentLength");
19 int count = (int)(length/segmentLength);
20 List points = TWaverUtil.divideShape(this.path, count);
21 if(points.size() < 2){
22 return;
23 }
24 int fillSegmentCount = (Integer)this.element.getClientProperty("fillSegmentCount");
25 double offset = (Double)this.element.getClientProperty("offset");
26 Color lineColor = (Color)this.element.getClientProperty("lineColor");
27 boolean from = (Boolean)this.element.getClientProperty("from");
28 boolean fromLeft = this.getFromPoint().x <= this.getToPoint().x;
29
30 g2d.setStroke(new BasicStroke((Float)this.element.getClientProperty("lineWidth")));
31 for(int i=0; i
32 Point2D point = (Point2D)points.get(i);
33 Point2D point1, point2;
34 double angle = 0;
35 if(i == points.size()-1){
36 point1 = (Point2D)points.get(i-1);
37 point2 = point;
38 } else {
39 point1 = point;
40 point2 = (Point2D)points.get(i+1);
41 }
42 angle = getAngle(point1, point2);
43 int sign = (fromLeft && from || !fromLeft && !from) ? -1 : 1;
44 if(angle == -Math.PI/2){
45 sign = point2.getY() > point1.getY() ? 1 : -1;
46 }else if(angle == Math.PI/2){
47 sign = point2.getY() > point1.getY() ? -1 : 1;
48 }
49 double alpha = (Double)this.element.getClientProperty("defaultAlpha");
50 if(offset * count >= i && offset * count - fillSegmentCount < = i){
51 alpha = 1 - (offset * count - i)/fillSegmentCount * 0.5;
52 }
53 g2d.setColor(new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), (int)(255 * alpha)));
54
55 AffineTransform transform = new AffineTransform();
56 transform.translate(point.getX(), point.getY());
57 transform.rotate(angle);
58 transform.translate(-point.getX(), -point.getY());
59
60 Point2D p0 = new Point.Double();
61 transform.transform(point, p0);
62 Point2D p1 = new Point.Double();
63 transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() - segmentLength/2), p1);
64 Point2D p2 = new Point.Double();
65 transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() + segmentLength/2), p2);
66 GeneralPath path = new GeneralPath();
67 path.moveTo(p1.getX(), p1.getY());
68 path.lineTo(p0.getX(), p0.getY());
69 path.lineTo(p2.getX(), p2.getY());
70 g2d.draw(path);
71 }
72 }
73
74 private static double getAngle(Point2D p1, Point2D p2) {
75 if(p1.getX() == p2.getX()){
76 if(p2.getY() == p1.getY()){
77 return 0;
78 }
79 else if(p2.getY() > p1.getY()){
80 return Math.PI/2;
81 }
82 else{
83 return -Math.PI/2;
84 }
85 }
86 return Math.atan((p2.getY() - p1.getY()) / (p2.getX() - p1.getX()));
87 }
88 }
有了這種流動(dòng)式的箭頭,我們就可以繪制出更多豐富多彩的界面,最后給出一個(gè)完整的例子供大伙學(xué)習(xí)參考:

注:附件中還給出了另一種Link的實(shí)現(xiàn)效果。
見原文最下方