2012/08/02

To Customize Flowing Link Style by Swing

At the thought of the styles of the line provided by Java2D, we may find them rather few, for there are no more styles other than straight line and dotted line. If you are careful enough, you will know that in TWaver we have provided a special link—the zigzag line.
We have a few ways for to realize the zigzag link. Still remember the varieties of customized LinkUIs mentioned in the previous articles? For example:
the colorful Link
the dot-flowing Link

Today we will introduce arrow-flowing Link. What’s that? Let’s first see the effect:
This is a Link flowing from the “from” node to the “to” node and it is composed of the arrows one by one. Don’t you think it’s better than the traditional way of flowing? Many clients have also mentioned this style. In this article we will explain to you the details.
At first, we need to customize a ArrowLink which inherits from Link, and then to define several parameters in ArrowLink, such as: the width of the line, the color of the line, the length of every arrow, the number of the flowing arrows needed, transparency, whether flowing from “from” node to “to” node and the offset (used to show the flowing), etc. Here is the code:
public class ArrowLink extends Link {

    public ArrowLink() {
        super();
        init();
    }

    public ArrowLink(Object id) {
        super(id);
        init();
    }

    public ArrowLink(Node from, Node to) {
        super(from, to);
        init();
    }

    public ArrowLink(Object id, Node from, Node to) {
        super(id, from, to);
        init();
    }

    private void init() {
        this.putLinkColor(new Color(0, 0, 0, 0));
        this.putLinkOutlineWidth(0);
        this.setLinkType(TWaverConst.LINK_TYPE_PARALLEL);
        this.putLinkAntialias(true);
        this.putClientProperty("lineWidth", 3.0f);
        this.putClientProperty("lineColor", Color.blue);
        this.putClientProperty("offset", 0.0);
        this.putClientProperty("segmentLength", 8.0);
        this.putClientProperty("fillSegmentCount", 5);
        this.putClientProperty("defaultAlpha", 0.2);
        this.putClientProperty("from", true);
    }

    public String getUIClassID() {
           return ArrowLinkUI.class.getName();
        }

}
Now the most important thing is to redraw LinkUI since the link has been customized: ArrowLinkUI class inherits from LinkUI and reloads the method paintBody. We need draw the arrows one by one in paintBody. In fact, it is quite easy to draw the arrows-- we can calculate the number of the arrows needed since we are able to obtain the length of Link and know the length of every arrow in the Link.
int count = (int)(length/segmentLength);
We can obtain the position of every point in the middle on an arrow according to the number of the arrows.
List points = TWaverUtil.divideShape(this.path, count);
After that, we are now able to calculate other two points at the top and the bottom of an arrow on the basis of the middle point.
Point2D p0 = new Point.Double();
transform.transform(point, p0);
Point2D p1 =  new Point.Double();
transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() - segmentLength/2), p1);
Point2D p2 =  new Point.Double();
transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() + segmentLength/2), p2);
An arrow can thus be drawn.
Other arrows can be drawn in the same way. But notice that arrows rotate with the position of the node. So we need to calculate the rotation angle of the arrows and the position of the rotation point.

AffineTransform transform = new AffineTransform();
transform.translate(point.getX(), point.getY());
transform.rotate(angle);
transform.translate(-point.getX(), -point.getY());
The last one is the flowing effect. Here we have set a parameter—offset, which can show the offset of flowing. We can make sure the transparency of the arrow according to the offset and the number of the flowing arrows.

double alpha = (Double)this.element.getClientProperty("defaultAlpha");
if(offset * count >= i && offset * count - fillSegmentCount <= i){
    alpha = 1 - (offset * count - i)/fillSegmentCount * 0.5;
}
The complete code for arrow drawing:

public class ArrowLinkUI extends LinkUI {

    public ArrowLinkUI(TNetwork network, Link link) {
        super(network, link);
    }

    public void paintBody(Graphics2D g2d) {
        super.paintBody(g2d);
        this.drawFlowing(g2d);
    }

    private void drawFlowing(Graphics2D g2d) {
        double length = TWaverUtil.getLength(this.path);
        if(length < =0 ){
            return;
        }

        double segmentLength = (Double)this.element.getClientProperty("segmentLength");
        int count = (int)(length/segmentLength);
        List points = TWaverUtil.divideShape(this.path, count);
        if(points.size() < 2){
            return;
        }
        int fillSegmentCount = (Integer)this.element.getClientProperty("fillSegmentCount");
        double offset = (Double)this.element.getClientProperty("offset");
        Color lineColor = (Color)this.element.getClientProperty("lineColor");
        boolean from = (Boolean)this.element.getClientProperty("from");
        boolean fromLeft = this.getFromPoint().x <= this.getToPoint().x;

        g2d.setStroke(new BasicStroke((Float)this.element.getClientProperty("lineWidth")));
        for(int i=0; i
            Point2D point = (Point2D)points.get(i);
            Point2D point1, point2;
            double angle = 0;
            if(i == points.size()-1){
                point1 = (Point2D)points.get(i-1);
                point2 = point;
            } else {
                point1 = point;
                point2 = (Point2D)points.get(i+1);
            }
            angle = getAngle(point1, point2);
            int sign = (fromLeft && from || !fromLeft && !from) ? -1 : 1;
            if(angle == -Math.PI/2){
                sign = point2.getY() > point1.getY() ? 1 : -1;
            }else if(angle == Math.PI/2){
                sign = point2.getY() > point1.getY() ? -1 : 1;
            }
            double alpha = (Double)this.element.getClientProperty("defaultAlpha");
            if(offset * count >= i && offset * count - fillSegmentCount < = i){
                alpha = 1 - (offset * count - i)/fillSegmentCount * 0.5;
            }
            g2d.setColor(new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), (int)(255 * alpha)));

            AffineTransform transform = new AffineTransform();
            transform.translate(point.getX(), point.getY());
            transform.rotate(angle);
            transform.translate(-point.getX(), -point.getY());

            Point2D p0 = new Point.Double();
            transform.transform(point, p0);
            Point2D p1 =  new Point.Double();
            transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() - segmentLength/2), p1);
            Point2D p2 =  new Point.Double();
            transform.transform(new Point.Double(point.getX() + segmentLength/2 * sign, point.getY() + segmentLength/2), p2);
            GeneralPath path = new GeneralPath();
            path.moveTo(p1.getX(), p1.getY());
            path.lineTo(p0.getX(), p0.getY());
            path.lineTo(p2.getX(), p2.getY());
            g2d.draw(path);
        }
    }

    private static double getAngle(Point2D p1, Point2D p2) {
        if(p1.getX() == p2.getX()){
            if(p2.getY() == p1.getY()){
                return 0;
            }
            else if(p2.getY() > p1.getY()){
                return Math.PI/2;
            }
            else{
                return -Math.PI/2;
            }
        }
        return Math.atan((p2.getY() - p1.getY()) / (p2.getX() - p1.getX()));
    }
}
With the help of this flowing arrows, we are now capable of drawing rich and colorful interface.

No comments:

Post a Comment