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.Stroke;
027import java.awt.geom.Rectangle2D;
028
029import org.biojava.bio.seq.Feature;
030import org.biojava.bio.symbol.Location;
031import org.biojava.utils.ChangeEvent;
032import org.biojava.utils.ChangeSupport;
033import org.biojava.utils.ChangeType;
034import org.biojava.utils.ChangeVetoException;
035
036/**
037 * <p><code>RectangularBeadRenderer</code> renders features as simple
038 * rectangles. Their outline and fill <code>Paint</code>,
039 * <code>Stroke</code>, feature depth, Y-axis displacement are
040 * configurable. The height of the rectangle will be equal to half its
041 * width, but not greater than the <code>beadDepth</code> set in the
042 * constructor.</p>
043 *
044 * <p>An alternative bead height behaviour is available where the
045 * rectangle height does not scale with its current width. The
046 * <code>setHeightScaling</code> method should be passed a boolean
047 * value to change this. The default is to use height scaling.</p>
048 *
049 * @author Keith James
050 * @since 1.2
051 */
052public class RectangularBeadRenderer extends AbstractBeadRenderer
053{
054    /**
055     * Constant <code>HEIGHTSCALING</code> indicating a change to the
056     * feature height scaling policy.
057     */
058    public static final ChangeType HEIGHTSCALING =
059        new ChangeType("The height scaling policy of the features has changed",
060                       "org.biojava.bio.gui.sequence.RectangularBeadRenderer",
061                       "HEIGHTSCALING", SequenceRenderContext.LAYOUT);
062
063    protected Rectangle2D rect;
064    protected boolean scaleHeight;
065
066    /**
067     * Creates a new <code>RectangularBeadRenderer</code> with the
068     * default settings.
069     */
070    public RectangularBeadRenderer()
071    {
072        super();
073        rect = new Rectangle2D.Double();
074        scaleHeight = true;
075    }
076
077    /**
078     * Creates a new <code>RectangularBeadRenderer</code>.
079     *
080     * @param beadDepth a <code>double</code>.
081     * @param beadDisplacement a <code>double</code>.
082     * @param beadOutline a <code>Paint</code>.
083     * @param beadFill a <code>Paint</code>.
084     * @param beadStroke a <code>Stroke</code>.
085     */
086    public RectangularBeadRenderer(double beadDepth,
087                                   double beadDisplacement,
088                                   Paint  beadOutline,
089                                   Paint  beadFill,
090                                   Stroke beadStroke)
091    {
092        super(beadDepth, beadDisplacement, beadOutline, beadFill, beadStroke);
093        rect = new Rectangle2D.Double();
094        scaleHeight = true;
095    }
096
097    /**
098     * <code>renderBead</code> renders features as simple rectangle.
099     *
100     * @param g2 a <code>Graphics2D</code>.
101     * @param f a <code>Feature</code> to render.
102     * @param context a <code>SequenceRenderContext</code> context.
103     */
104    public void renderBead(Graphics2D            g2,
105                           Feature               f,
106                           SequenceRenderContext context)
107    {
108        Location loc = f.getLocation();
109
110        int min = loc.getMin();
111        int max = loc.getMax();
112        int dif = max - min;
113
114        double posXW, posYN, width, height;
115
116        if (context.getDirection() == SequenceRenderContext.HORIZONTAL)
117        {
118            posXW  = context.sequenceToGraphics(min);
119            posYN  = beadDisplacement;
120            width  = Math.max(((double) (dif + 1)) * context.getScale(), 1.0);
121
122            if (scaleHeight)
123            {
124                height = Math.min(beadDepth, width / 2.0);
125
126                // If the bead height occupies less than the full height
127                // of the renderer, move it down so that it is central
128                if (height < beadDepth)
129                    posYN += ((beadDepth - height) / 2.0);
130            }
131            else
132            {
133                height = beadDepth;
134            }
135
136            rect.setRect(posXW, posYN,
137                         Math.floor(width),
138                         Math.floor(height));
139        }
140        else
141        {
142            posXW  = beadDisplacement;
143            posYN  = context.sequenceToGraphics(min);
144            height = Math.max(((double) dif + 1) * context.getScale(), 1.0);
145
146            if (scaleHeight)
147            {
148                width = Math.min(beadDepth, height / 2.0);
149
150                if (width < beadDepth)
151                    posXW += ((beadDepth - width) /  2.0);
152            }
153            else
154            {
155                width = beadDepth;
156            }
157
158            rect.setRect(posXW, posYN,
159                         Math.floor(width),
160                         Math.floor(height));
161        }
162
163        g2.setPaint(beadFill);
164        g2.fill(rect);
165
166        g2.setStroke(beadStroke);
167        g2.setPaint(beadOutline);
168        g2.draw(rect);
169    }
170
171    /**
172     * <code>getDepth</code> calculates the depth required by this
173     * renderer to display its beads.
174     *
175     * @param context a <code>SequenceRenderContext</code>.
176     *
177     * @return a <code>double</code>.
178     */
179    public double getDepth(SequenceRenderContext context)
180    {
181        // Get max depth of delegates using base class method
182        double maxDepth = super.getDepth(context);
183        return Math.max(maxDepth, (beadDepth + beadDisplacement));
184    }
185
186    /**
187     * <code>getHeightScaling</code> returns the state of the height
188     * scaling policy.
189     *
190     * @return a <code>boolean</code> true if height scaling is
191     * enabled.
192     */
193    public boolean getHeightScaling()
194    {
195        return scaleHeight;
196    }
197
198    /**
199     * <code>setHeightScaling</code> sets the height scaling
200     * policy. Default behaviour is for this to be enabled leading to
201     * features being drawn with a height equal to half their width,
202     * subject to a maximum height restriction equal to the
203     * <code>beadDepth</code> set in the constructor. If disabled,
204     * features will always be drawn at the maximum height allowed by
205     * the <code>beadDepth</code> parameter.
206     *
207     * @param isEnabled a <code>boolean</code>.
208     *
209     * @exception ChangeVetoException if an error occurs.
210     */
211    public void setHeightScaling(boolean isEnabled) throws ChangeVetoException
212    {
213        if (hasListeners())
214        {
215            ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
216            synchronized(cs)
217            {
218                ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT,
219                                                 null, null,
220                                                 new ChangeEvent(this, HEIGHTSCALING,
221                                                                 new Boolean(scaleHeight),
222                                                                 new Boolean(isEnabled)));
223                cs.firePreChangeEvent(ce);
224                scaleHeight = isEnabled;
225                cs.firePostChangeEvent(ce);
226            }
227        }
228        else
229        {
230            scaleHeight = isEnabled;
231        }
232    }
233}