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 */
021
022package org.biojava.bio.gui.sequence;
023
024import java.awt.Graphics2D;
025import java.awt.Paint;
026import java.awt.Shape;
027import java.awt.Stroke;
028import java.awt.geom.Ellipse2D;
029
030import org.biojava.bio.seq.Feature;
031import org.biojava.bio.symbol.Location;
032import org.biojava.utils.ChangeEvent;
033import org.biojava.utils.ChangeSupport;
034import org.biojava.utils.ChangeType;
035import org.biojava.utils.ChangeVetoException;
036
037/**
038 * <p><code>EllipticalBeadRenderer</code> renders features as simple
039 * ellipses. Their outline and fill <code>Paint</code>,
040 * <code>Stroke</code>, feature depth, Y-axis displacement are
041 * configurable. Also configurable is the minimum ratio of long axis
042 * to short axis of the ellipse - this prevents long features also
043 * becoming ever wider and obscuring neighbours.</p>
044 *
045 * @author Keith James
046 * @since 1.2
047 */
048public class EllipticalBeadRenderer extends AbstractBeadRenderer
049{
050    /**
051     * Constant <code>RATIO</code> indicating a change to the minimum
052     * allowed ratio of long axis to short axis of the features.
053     */
054    public static final ChangeType RATIO =
055        new ChangeType("The shape of the features has changed",
056                       "org.biojava.bio.gui.sequence.EllipticalBeadRenderer",
057                       "RATIO", SequenceRenderContext.LAYOUT);
058
059    protected double dimensionRatio;
060
061    /**
062     * Creates a new <code>EllipticalBeadRenderer</code> object
063     * with the default settings.
064     */
065    public EllipticalBeadRenderer()
066    {
067        super();
068        dimensionRatio = 2.0F;
069    }
070
071    /**
072     * Creates a new <code>EllipticalBeadRenderer</code>.
073     *
074     * @param beadDepth a <code>double</code>.
075     * @param beadDisplacement a <code>double</code>.
076     * @param beadOutline a <code>Paint</code>.
077     * @param beadFill a <code>Paint</code>.
078     * @param beadStroke a <code>Stroke</code>.
079     * @param dimensionRatio a <code>double</code>.
080     */
081    public EllipticalBeadRenderer(double beadDepth,
082                                  double beadDisplacement,
083                                  Paint  beadOutline,
084                                  Paint  beadFill,
085                                  Stroke beadStroke,
086                                  double dimensionRatio)
087    {
088        super(beadDepth, beadDisplacement, beadOutline, beadFill, beadStroke);
089        dimensionRatio = 2.0F;
090    }
091
092    /**
093     * <code>renderBead</code> renders features as simple ellipse.
094     *
095     * @param g2 a <code>Graphics2D</code> context.
096     * @param f a <code>Feature</code> to render.
097     * @param context a <code>SequenceRenderContext</code> context.
098     */
099    public void renderBead(Graphics2D            g2,
100                           Feature               f,
101                           SequenceRenderContext context)
102    {
103        Location loc = f.getLocation();
104
105        int min = loc.getMin();
106        int max = loc.getMax();
107        int dif = max - min;
108
109        Shape shape;
110
111        if (context.getDirection() == SequenceRenderContext.HORIZONTAL)
112        {
113            double  posXW = context.sequenceToGraphics(min);
114            double  posYN = beadDisplacement;
115            double  width = Math.max(((double) (dif + 1)) * context.getScale(), 1.0f);
116            double height = Math.min(beadDepth, width / dimensionRatio);
117
118            // If the bead height occupies less than the full height
119            // of the renderer, move it down so that it is central
120            if (height < beadDepth)
121                posYN += ((beadDepth - height) / dimensionRatio);
122
123            shape = new Ellipse2D.Double(posXW, posYN, width, height);
124        }
125        else
126        {
127            double  posXW = beadDisplacement;
128            double  posYN = context.sequenceToGraphics(min);
129            double height = Math.max(((double) dif + 1) * context.getScale(), 1.0f);
130            double  width = Math.min(beadDepth, height / dimensionRatio);
131
132            if (width < beadDepth)
133                posXW += ((beadDepth - width) /  dimensionRatio);
134
135            shape = new Ellipse2D.Double(posXW, posYN, width, height);
136        }
137
138        g2.setPaint(beadFill);
139        g2.fill(shape);
140
141        g2.setStroke(beadStroke);
142        g2.setPaint(beadOutline);
143        g2.draw(shape);
144    }
145
146    /**
147     * <code>getDepth</code> calculates the depth required by this
148     * renderer to display its beads.
149     *
150     * @param context a <code>SequenceRenderContext</code> object.
151     *
152     * @return a <code>double</code>.
153     */
154    public double getDepth(SequenceRenderContext context)
155    {
156        // Get max depth of delegates using base class method
157        double maxDepth = super.getDepth(context);
158        return Math.max(maxDepth, (beadDepth + beadDisplacement));
159    }
160
161    /**
162     * <code>getDimensionRatio</code> returns the maximum ratio of
163     * long dimension to short dimension of the bead. This should be
164     * equal, or greater than 1.
165     *
166     * @return a <code>double</code>.
167     */
168    public double getDimensionRatio()
169    {
170        return dimensionRatio;
171    }
172
173    /**
174     * <code>setDimensionRatio</code> sets the minimum ratio of
175     * long dimension to short dimension of the bead. This should be
176     * equal, or greater than 1.
177     *
178     * @param ratio a <code>double</code> ratio of depth.
179     *
180     * @exception ChangeVetoException if an error occurs.
181     */
182    public void setDimensionRatio(double ratio) throws ChangeVetoException
183    {
184        if (ratio < 1.0F)
185            throw new ChangeVetoException("The long dimension may not be less than the short dimension (ratio >= 1.0)");
186
187        if (hasListeners())
188        {
189            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
190            synchronized(cs)
191            {
192                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
193                                                 null, null,
194                                                 new ChangeEvent(this, RATIO,
195                                                                 new Double(dimensionRatio),
196                                                                 new Double(ratio)));
197                cs.firePreChangeEvent(ce);
198                dimensionRatio= ratio;
199                cs.firePostChangeEvent(ce);
200            }
201        }
202        else
203        {
204            dimensionRatio = ratio;
205        }
206    }
207}