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.Color;
025import java.awt.Graphics2D;
026import java.awt.Paint;
027import java.awt.event.MouseEvent;
028import java.awt.geom.Line2D;
029import java.awt.geom.Point2D;
030import java.awt.geom.Rectangle2D;
031import java.util.Iterator;
032
033import org.biojava.bio.seq.Feature;
034import org.biojava.bio.seq.FeatureHolder;
035import org.biojava.bio.seq.StrandedFeature;
036import org.biojava.bio.symbol.Location;
037import org.biojava.utils.AbstractChangeable;
038import org.biojava.utils.ChangeEvent;
039import org.biojava.utils.ChangeSupport;
040import org.biojava.utils.ChangeVetoException;
041
042/**
043 * A feature renderer that draws non-contiguous features as a set of boxes
044 * joined by zig-zags.
045 * <p>
046 * This is applicable to rendering cds's or non-contiguous homologies for
047 * example.
048 *
049 * @author Matthew Pocock
050 */
051public class ZiggyFeatureRenderer
052extends AbstractChangeable
053implements FeatureRenderer, java.io.Serializable {
054  private Paint outline = Color.black;
055  private Paint fill = Color.yellow;
056  private double blockDepth = 10.0;
057
058  public void setFill(Paint p)
059  throws ChangeVetoException {
060    if(hasListeners()) {
061      ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
062      synchronized(cs) {
063        ChangeEvent ce = new ChangeEvent(
064          this, SequenceRenderContext.REPAINT,
065          p, this.fill
066        );
067        cs.firePreChangeEvent(ce);
068        this.fill = p;
069        cs.firePostChangeEvent(ce);
070      }
071    } else {
072      this.fill = p;
073    }
074  }
075
076  public Paint getFill() {
077    return fill;
078  }
079
080  public void setOutline(Paint p)
081  throws ChangeVetoException {
082    if(hasListeners()) {
083      ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT);
084      synchronized(cs) {
085        ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT);
086        cs.firePreChangeEvent(ce);
087        this.outline = p;
088        cs.firePostChangeEvent(ce);
089      }
090    } else {
091      this.outline = p;
092    }
093  }
094
095  public Paint getOutline() {
096    return outline;
097  }
098
099  public void setBlockDepth(double depth)
100  throws ChangeVetoException {
101    if(hasListeners()) {
102      ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
103      synchronized(cs) {
104        ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT);
105        cs.firePreChangeEvent(ce);
106        this.blockDepth = depth;
107        cs.firePostChangeEvent(ce);
108      }
109    } else {
110      this.blockDepth = depth;
111    }
112  }
113
114  public double getBlockDepth() {
115    return blockDepth;
116  }
117
118  public double getDepth(SequenceRenderContext src) {
119    return blockDepth + 1.0;
120  }
121
122  public void renderFeature(
123    Graphics2D g, Feature f, SequenceRenderContext context
124  ) {
125    Location loc = f.getLocation();
126    Iterator i = loc.blockIterator();
127    Location last = null;
128    if(i.hasNext()) {
129      last = (Location) i.next();
130      renderLocation(g, last, context);
131    }
132    while(i.hasNext()) {
133      Location next = (Location) i.next();
134      renderLink(g, f, last, next, context);
135      renderLocation(g, next, context);
136      last = next;
137    }
138  }
139
140  private void renderLocation(
141    Graphics2D g, Location loc, SequenceRenderContext context
142  ) {
143    Rectangle2D.Double block = new Rectangle2D.Double();
144    double min = context.sequenceToGraphics(loc.getMin());
145    double max = context.sequenceToGraphics(loc.getMax()+1);
146    if(context.getDirection() == SequenceRenderContext.HORIZONTAL) {
147      block.setFrame(
148        min, 0.0,
149        max - min, blockDepth
150      );
151    } else {
152      block.setFrame(
153        0.0, min,
154        blockDepth, max - min
155      );
156    }
157    if(fill != null) {
158      g.setPaint(fill);
159      g.fill(block);
160    }
161    if(outline != null) {
162      g.setPaint(outline);
163      g.draw(block);
164    }
165  }
166
167    private StrandedFeature.Strand getStrand(Feature f) {
168        if (f instanceof StrandedFeature) {
169            return ((StrandedFeature) f).getStrand();
170        } else {
171            FeatureHolder fh = f.getParent();
172            if (fh instanceof Feature) {
173                return getStrand((Feature) fh);
174            } else {
175                return StrandedFeature.UNKNOWN;
176            }
177        }
178    }
179
180  private void renderLink(
181    Graphics2D g, Feature f, Location source, Location dest,
182    SequenceRenderContext context
183  ) {
184    Line2D line = new Line2D.Double();
185    Point2D startP;
186    Point2D midP;
187    Point2D endP;
188    double half = blockDepth * 0.5;
189    if(context.getDirection() == SequenceRenderContext.HORIZONTAL) {
190      if(getStrand(f) == StrandedFeature.NEGATIVE) {
191        double start = context.sequenceToGraphics(dest.getMin());
192        double end = context.sequenceToGraphics(source.getMax()+1);
193        double mid = (start + end) * 0.5;
194        startP = new Point2D.Double(start, half);
195        midP   = new Point2D.Double(mid,   blockDepth);
196        endP   = new Point2D.Double(end,   half);
197      } else {
198        double start = context.sequenceToGraphics(source.getMax()+1);
199        double end = context.sequenceToGraphics(dest.getMin());
200        double mid = (start + end) * 0.5;
201        startP = new Point2D.Double(start, half);
202        midP   = new Point2D.Double(mid,   0.0);
203        endP   = new Point2D.Double(end,   half);
204      }
205    } else {
206      if (getStrand(f) == StrandedFeature.NEGATIVE) {
207        double start = context.sequenceToGraphics(dest.getMin()+1);
208        double end = context.sequenceToGraphics(source.getMax());
209        double mid = (start + end) * 0.5;
210        startP = new Point2D.Double(half,       start);
211        midP   = new Point2D.Double(blockDepth, mid);
212        endP   = new Point2D.Double(half,       end);
213      } else {
214        double start = context.sequenceToGraphics(source.getMax());
215        double end = context.sequenceToGraphics(dest.getMin()+1);
216        double mid = (start + end) * 0.5;
217        startP = new Point2D.Double(half, start);
218        midP   = new Point2D.Double(0.0,  mid);
219        endP   = new Point2D.Double(half, end);
220      }
221    }
222    g.setPaint(getOutline());
223    line.setLine(startP, midP);
224    g.draw(line);
225    line.setLine(midP, endP);
226    g.draw(line);
227  }
228
229  public FeatureHolder processMouseEvent(
230    FeatureHolder hits,
231    SequenceRenderContext src,
232    MouseEvent me
233  ) {
234    return hits;
235  }
236}