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.Shape; 026import java.awt.event.MouseEvent; 027import java.awt.geom.Rectangle2D; 028import java.util.Iterator; 029import java.util.List; 030 031import org.biojava.bio.seq.FeatureHolder; 032 033/** 034 * <code>LayeredRenderer</code> handles the lane offsets for 035 * <code>MultiLineRender</code>s. For each successive lane it 036 * translates the <code>Graphics2D</code> perpendicular to the 037 * sequence rendering direction by an amount equal to the value 038 * returned by the <code>getDepth()</code> method of that lane's 039 * renderer. 040 * 041 * @author Matthew Pocock 042 * @author Keith James 043 * @since 1.1 044 */ 045public class LayeredRenderer { 046 047 /** 048 * Static <code>LayeredRenderer</code> <code>INSTANCE</code> used 049 * by <code>MultiLineRenderer</code>s. 050 */ 051 public static final LayeredRenderer INSTANCE = new LayeredRenderer(); 052 053 /** 054 * <code>getDepth</code> returns the total depth of a list of 055 * <code>SequenceRenderer</code>s. 056 * 057 * @param srcL a <code>List</code> of 058 * <code>SequenceRenderContext</code>s. 059 * @param renderers a <code>List</code> of 060 * <code>SequenceRenderer</code>s. 061 * 062 * @return a <code>double</code>. 063 */ 064 public double getDepth(List srcL, List renderers) { 065 if (srcL.size() != renderers.size()) { 066 throw new IllegalArgumentException("srcL and renderers must be the same size: " + 067 srcL.size() + ":" + renderers.size()); 068 } 069 070 double depth = 0.0; 071 Iterator srcI = srcL.iterator(); 072 Iterator i = renderers.iterator(); 073 074 while (srcI.hasNext() && i.hasNext()) { 075 SequenceRenderContext src = (SequenceRenderContext) srcI.next(); 076 SequenceRenderer sRend = (SequenceRenderer) i.next(); 077 if(sRend instanceof OverlayMarker){ 078 depth += 0.0; // maybe just do nothing here 079 }else { 080 depth += sRend.getDepth(src); 081 } 082 } 083 return depth; 084 } 085 086 /** 087 * <code>getMinimumLeader</code> returns the maximum value of 088 * getMinimumLeader() for a list of <code>SequenceRenderer</code>s. 089 * 090 * @param srcL a <code>List</code> of 091 * <code>SequenceRenderContext</code>s. 092 * @param renderers a <code>List</code> of 093 * <code>SequenceRenderer</code>s. 094 * 095 * @return a <code>double</code>. 096 */ 097 public double getMinimumLeader(List srcL, List renderers) { 098 if (srcL.size() != renderers.size()) { 099 throw new IllegalArgumentException("srcL and renderers must be the same size: " + 100 srcL.size() + ":" + renderers.size()); 101 } 102 103 double max = 0.0; 104 Iterator srcI = srcL.iterator(); 105 Iterator i = renderers.iterator(); 106 107 while (srcI.hasNext() && i.hasNext()) { 108 SequenceRenderContext src = (SequenceRenderContext) srcI.next(); 109 SequenceRenderer sRend = (SequenceRenderer) i.next(); 110 max = Math.max(max, sRend.getMinimumLeader(src)); 111 } 112 return max; 113 } 114 115 /** 116 * <code>getMinimumTrailer</code> returns the maximum value of 117 * getMinimumTrailer() for a list of <code>SequenceRenderer</code>s. 118 * 119 * @param srcL a <code>List</code> of 120 * <code>SequenceRenderContext</code>s. 121 * @param renderers a <code>List</code> of 122 * <code>SequenceRenderer</code>s. 123 * 124 * @return a <code>double</code>. 125 */ 126 public double getMinimumTrailer(List srcL, List renderers) { 127 if (srcL.size() != renderers.size()) { 128 throw new IllegalArgumentException("srcL and renderers must be the same size: " + 129 srcL.size() + ":" + renderers.size()); 130 } 131 132 double max = 0.0; 133 Iterator srcI = srcL.iterator(); 134 Iterator i = renderers.iterator(); 135 136 while (srcI.hasNext() && i.hasNext()) { 137 SequenceRenderContext src = (SequenceRenderContext) srcI.next(); 138 SequenceRenderer sRend = (SequenceRenderer) i.next(); 139 max = Math.max(max, sRend.getMinimumTrailer(src)); 140 } 141 return max; 142 } 143 144 public void paint(Graphics2D g, List srcL, List renderers) { 145 if (srcL.size() != renderers.size()) { 146 throw new IllegalArgumentException("srcL and renderers must be the same size: " + 147 srcL.size() + ":" + renderers.size()); 148 } 149 150 // Offset perpendicular to sequence rendering direction 151 double offset = 0.0; 152 // Don't know what this is 153 double allocatedOffset = 0.0; 154 155 Iterator srcI = srcL.iterator(); 156 Iterator i = renderers.iterator(); 157 158 // New clipping rectangle 159 Rectangle2D clip = new Rectangle2D.Double(); 160 161 while (srcI.hasNext() && i.hasNext()) { 162 SequenceRenderContext src = (SequenceRenderContext) srcI.next(); 163 SequenceRenderer sRend = (SequenceRenderer) i.next(); 164 int dir = src.getDirection(); 165 double depth = sRend.getDepth(src); 166 167 // Sequence range should be inclusive of the min/max 168 // coordinates for sequenceToGraphics() so we use 169 // src.getRange().getMax() + 1 170 double minP = src.sequenceToGraphics(src.getRange().getMin()) - 171 sRend.getMinimumLeader(src); 172 double maxP = src.sequenceToGraphics(src.getRange().getMax() + 1) + 173 sRend.getMinimumTrailer(src); 174 175 // Added +1 to these as the outer edge of features was 176 // being clipped off 177 if (dir == SequenceRenderContext.HORIZONTAL) { 178 clip.setFrame(minP, 0.0, maxP - minP + 1, depth + 1); 179 g.translate(0.0, offset); 180 } else { 181 clip.setFrame(0.0, minP, depth + 1, maxP - minP + 1); 182 g.translate(offset, 0.0); 183 } 184 185 Shape oldClip = g.getClip(); 186 g.clip(clip); 187 sRend.paint(g, src); 188 g.setClip(oldClip); 189 190 if (dir == SequenceRenderContext.HORIZONTAL) { 191 g.translate(0.0, -offset); 192 } else { 193 g.translate(-offset, 0.0); 194 } 195 196 if (sRend instanceof OverlayMarker) { 197 // overlay, just record maximum allocation 198 allocatedOffset = Math.max(allocatedOffset, sRend.getDepth(src)); 199 } else { 200 // non-overlaid: apply all relevant offsets 201 offset += Math.max(sRend.getDepth(src), allocatedOffset); 202 allocatedOffset = 0.0; // clear it as it is now applied. 203 } 204 } 205 } 206 207 public SequenceViewerEvent processMouseEvent(List srcL, MouseEvent me, 208 List path, List renderers) { 209 if (srcL.size() != renderers.size()) { 210 throw new IllegalArgumentException("srcL and renderers must be the same size: " + 211 srcL.size() + ":" + renderers.size()); 212 } 213 214 // Offset perpendicular to sequence rendering direction 215 double offset = 0.0; 216 217 Iterator srcI = srcL.iterator(); 218 Iterator i = renderers.iterator(); 219 220 SequenceViewerEvent sve = null; 221 222 while (srcI.hasNext() && i.hasNext()) { 223 SequenceRenderContext src = (SequenceRenderContext) srcI.next(); 224 SequenceRenderer sRend = (SequenceRenderer) i.next(); 225 double depth = sRend.getDepth(src); 226 227 SequenceViewerEvent thisSve = null; 228 if (src.getDirection() == SequenceRenderContext.HORIZONTAL) { 229 if ((me.getY() >= offset) && (me.getY() < offset + depth)) { 230 me.translatePoint(0, (int) -offset); 231 thisSve = sRend.processMouseEvent(src, me, path); 232 me.translatePoint(0, (int) +offset); 233 } 234 } else { 235 if ((me.getX() >= offset) && (me.getX() < offset + depth)) { 236 me.translatePoint((int) -offset, 0); 237 thisSve = sRend.processMouseEvent(src, me, path); 238 me.translatePoint((int) +offset, 0); 239 } 240 } 241 242 if (thisSve != null) { 243 if (sve == null) { 244 sve = thisSve; 245 } else if (thisSve.getTarget() instanceof FeatureHolder && 246 ((FeatureHolder) thisSve.getTarget()).countFeatures() > 0) 247 { 248 // features trump anything else 249 sve = thisSve; 250 } 251 } 252 253 if (! (sRend instanceof OverlayMarker)) offset += depth; 254 } 255 return sve; 256 } 257} 258