001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.bio.gui.sequence;
022
023import java.awt.Color;
024import java.awt.Graphics2D;
025import java.awt.Paint;
026import java.awt.Shape;
027import java.awt.event.MouseEvent;
028import java.awt.geom.Area;
029import java.awt.geom.GeneralPath;
030import java.awt.geom.Rectangle2D;
031
032import org.biojava.bio.seq.Feature;
033import org.biojava.bio.seq.FeatureHolder;
034import org.biojava.bio.symbol.Location;
035
036
037/**
038 * A Feature Renderer that paints the Feature as a right facing arrow Based heavily on
039 * BasicFeatureRenderer
040 *
041 * @author Mark Southern
042 * @since 1.5
043 */
044public class ArrowedFeatureRenderer implements FeatureRenderer {
045    private Paint fill;
046    private Paint outline;
047    private double arrowSize = 15.0;
048    private double arrowScoop = 4.0;
049    private double arrowHeadWidth = 5;
050
051    public ArrowedFeatureRenderer() {
052        fill = Color.red;
053        outline = Color.black;
054    }
055
056    public void setFill(Paint p) {
057        fill = p;
058    }
059
060    public Paint getFill() {
061        return fill;
062    }
063
064    public void setOutline(Paint p) {
065        outline = p;
066    }
067
068    public Paint getOutline() {
069        return outline;
070    }
071
072    public void setArrowSize(double arrowSize) {
073        this.arrowSize = arrowSize;
074    }
075
076    public double getArrowSize() {
077        return arrowSize;
078    }
079
080    public void setArrowScoop(double arrowScoop) {
081        this.arrowScoop = arrowScoop;
082    }
083
084    public double getArrowScoop() {
085        return arrowScoop;
086    }
087
088    public void setArrowHeadSize(double d) {
089        this.arrowHeadWidth = d;
090    }
091
092    public double getArrowHeadSize() {
093        return arrowHeadWidth;
094    }
095
096    public void renderFeature(Graphics2D g, Feature f, SequenceRenderContext src) {
097        Shape s = null;
098        Location loc = f.getLocation();
099        float depth = ( float ) (arrowSize + (2.0 * arrowScoop));
100
101        double minD;
102        double maxD;
103
104        if (src.getScale() > 1.0) {
105            minD = src.sequenceToGraphics(loc.getMin());
106            maxD = src.sequenceToGraphics(loc.getMax() + 1) - 1.0;
107        } else {
108            minD = src.sequenceToGraphics(loc.getMin());
109            maxD = src.sequenceToGraphics(loc.getMax());
110        }
111
112        float min = ( float ) minD;
113        float max = ( float ) maxD;
114
115        float minBounds = ( float ) src.sequenceToGraphics(src.getRange().getMin() - 1);
116        float maxBounds = ( float ) src.sequenceToGraphics(src.getRange().getMax() + 1);
117        Shape bounds;
118
119        if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
120            bounds = new Rectangle2D.Double(minBounds, 0, maxBounds - minBounds, depth);
121        } else {
122            bounds = new Rectangle2D.Double(0, minBounds, depth, maxBounds - minBounds);
123        }
124
125        if ((max - min) >= arrowSize) {
126            if (src.getDirection() == SequenceRenderContext.HORIZONTAL) {
127                float minY = 0.0f;
128                float maxY = depth;
129                float minYS = minY + ( float ) arrowScoop;
130                float maxYS = maxY - ( float ) arrowScoop;
131                float midY = (minY + maxY) * 0.5f;
132                float minX = min;
133                float maxX = max;
134
135                float midX1 = maxX - ( float ) getArrowHeadSize();
136                float midX2 = minX + ( float ) getArrowHeadSize();
137
138                GeneralPath path = new GeneralPath();
139                path.moveTo(minX, midY);
140                path.lineTo(midX2, minY);
141                path.lineTo(midX2, minYS);
142                path.lineTo(midX1, minYS);
143                path.lineTo(midX1, minY);
144                path.lineTo(maxX, midY);
145                path.lineTo(midX1, maxY);
146                path.lineTo(midX1, maxYS);
147                path.lineTo(midX2, maxYS);
148                path.lineTo(midX2, maxY);
149                path.closePath();
150                s = path;
151            }
152        }
153
154        if (! bounds.contains(s.getBounds())) {
155            //  System.err.println("Edgeclipping");
156            s = new Area(s);
157            (( Area ) s).intersect(new Area(bounds));
158        }
159
160        if (fill != null) {
161            g.setPaint(fill);
162            g.fill(s);
163        }
164
165        if ((outline != null) && ((maxD - minD) > 4.0)) {
166            g.setPaint(outline);
167            g.draw(s);
168        } else {
169            //  System.err.println("Not drawing outline...");
170        }
171    }
172
173    public double getDepth(SequenceRenderContext src) {
174        return arrowSize + (2.0 * arrowScoop) + 1.0;
175    }
176
177    public FeatureHolder processMouseEvent(FeatureHolder hits, SequenceRenderContext src,
178        MouseEvent me
179    ) {
180        return hits;
181    }
182}