diff --git a/uk/ac/sanger/artemis/components/alignment/JamView.java b/uk/ac/sanger/artemis/components/alignment/JamView.java index e73eaf3171c5020bd8b485f0098686d824e6c81b..8d8f33cde2905461f152144f71a464c2516a2f90 100644 --- a/uk/ac/sanger/artemis/components/alignment/JamView.java +++ b/uk/ac/sanger/artemis/components/alignment/JamView.java @@ -5,9 +5,11 @@ import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; +import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; +import java.awt.Rectangle; import java.awt.Stroke; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; @@ -28,6 +30,9 @@ import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; +import javax.swing.UIManager; import uk.ac.sanger.artemis.Entry; import uk.ac.sanger.artemis.EntryGroup; @@ -43,6 +48,7 @@ import uk.ac.sanger.artemis.util.DocumentFactory; import uk.ac.sanger.artemis.util.OutOfRangeException; public class JamView extends JPanel + implements Scrollable { private static final long serialVersionUID = 1L; private List<Read> readsInView; @@ -54,6 +60,11 @@ public class JamView extends JPanel private JScrollPane jspView; private JComboBox combo; private int nbasesInView; + private int laststart; + private int lastend; + private int maxUnitIncrement = 4; + private int ALIGNMENT_PIX_PER_BASE; + public JamView(String bam, String reference, @@ -77,6 +88,23 @@ public class JamView extends JPanel } } readHeader(); + + final javax.swing.plaf.FontUIResource font_ui_resource = + Options.getOptions().getFontUIResource(); + + java.util.Enumeration keys = UIManager.getDefaults().keys(); + while(keys.hasMoreElements()) + { + Object key = keys.nextElement(); + Object value = UIManager.get(key); + if(value instanceof javax.swing.plaf.FontUIResource) + UIManager.put(key, font_ui_resource); + } + FontMetrics fm = getFontMetrics(getFont()); + int boundWidth = fm.stringWidth("A")/5; + ALIGNMENT_PIX_PER_BASE = fm.stringWidth("A")+boundWidth; + + System.out.println(getFont().getFontName()); } private void readHeader() @@ -122,7 +150,7 @@ public class JamView extends JPanel } } - private void readFromBam(int start, int end) + private void readFromBam(int start, int end, boolean pair_sort) { String refName = (String) combo.getSelectedItem(); String cmd[] = { "/Users/tjc/BAM/samtools-0.1.5c/samtools", @@ -162,8 +190,9 @@ public class JamView extends JPanel pread.seq = fields[9]; readsInView.add(pread); } - - Collections.sort(readsInView, new ReadComparator()); + + if(pair_sort) + Collections.sort(readsInView, new ReadComparator()); } catch (IOException e) { @@ -183,41 +212,121 @@ public class JamView extends JPanel int seqLength = seqLengths.get(refName); float pixPerBase = ((float)getWidth())/(float)(seqLength); - double x = jspView.getViewport().getViewRect().getX(); - - int start = (int) (seqLength * ( x / getWidth())); - int end = (int) (start + (jspView.getViewport().getWidth() / pixPerBase)); - int scaleHeight = 15; - readFromBam(start, end); - - drawScale(g2, start, end, pixPerBase); - - Stroke originalStroke = g2.getStroke(); - Stroke stroke = - new BasicStroke (2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); + if(pixPerBase >= ALIGNMENT_PIX_PER_BASE) + drawAlignment(g2, seqLength, pixPerBase); + else + drawLineView(g2, seqLength, pixPerBase); + } + + private void drawAlignment(Graphics2D g2, int seqLength, float pixPerBase) + { + double x = jspView.getViewport().getViewRect().getX(); + int start = (int) (seqLength * ( (float)x / (float)getWidth())); + int end = (int) (start + ((float)jspView.getViewport().getWidth() / + (float)pixPerBase)); - for(int i=0; i<readsInView.size(); i++) - { + if(laststart != start || + lastend != end) + readFromBam(start, end, false); + + laststart = start; + lastend = end; + + int ypos = 1; + + boolean draw[] = new boolean[readsInView.size()]; + for(int i=0; i<readsInView.size(); i++) + draw[i] = false; + + for(int i=0; i<readsInView.size(); i++) + { + if (!draw[i]) + { + Read thisRead = readsInView.get(i); + ypos = ypos + 10; + + drawSequence(g2, thisRead, pixPerBase, ypos); + draw[i] = true; + + int thisEnd = thisRead.pos+thisRead.seq.length(); + for(int j=i+1; j<readsInView.size(); j++) + { + if (!draw[j]) + { + Read nextRead = readsInView.get(j); + if(nextRead.pos > thisEnd+1) + { + drawSequence(g2, nextRead, pixPerBase, ypos); + draw[j] = true; + break; + } + } + } + } + } + } + + private void drawSequence(Graphics2D g2, Read read, float pixPerBase, int ypos) + { + if ((read.flag & 0x0001) != 0x0001 || + (read.flag & 0x0008) == 0x0008) + g2.setColor(Color.black); + else + g2.setColor(Color.blue); + + int xpos; + + for(int i=0;i<read.seq.length(); i++) + { + xpos = ((read.pos-1) + i)*ALIGNMENT_PIX_PER_BASE; + g2.drawString(read.seq.substring(i, i+1), xpos, ypos); + } + + } + + private void drawLineView(Graphics2D g2, int seqLength, float pixPerBase) + { + double x = jspView.getViewport().getViewRect().getX(); + int start = (int) (seqLength * ( (float)x / (float)getWidth())); + int end = (int) (start + ((float)jspView.getViewport().getWidth() / + (float)pixPerBase)); + + if(laststart != start || + lastend != end) + readFromBam(start, end, true); + + laststart = start; + lastend = end; + + drawScale(g2, start, end, pixPerBase); + + Stroke originalStroke = g2.getStroke(); + Stroke stroke = + new BasicStroke (2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); + int scaleHeight = 15; + + for(int i=0; i<readsInView.size(); i++) + { Read thisRead = readsInView.get(i); Read nextRead = null; if( (thisRead.flag & 0x0001) != 0x0001 || - (thisRead.flag & 0x0008) == 0x0008 ) // not single read - continue; + (thisRead.flag & 0x0008) == 0x0008 ) // not single read + continue; if(i < readsInView.size()-1) { - nextRead = readsInView.get(i+1); - i++; + nextRead = readsInView.get(i+1); + i++; } if(nextRead != null && (nextRead.flag & 0x0001) == 0x0001 && (nextRead.flag & 0x0008) != 0x0008 ) { - if(thisRead.qname.equals(nextRead.qname)) - { + if(thisRead.qname.equals(nextRead.qname)) + { if( (thisRead.flag & 0x0010) == 0x0010 && (nextRead.flag & 0x0010) == 0x0010 ) g2.setColor(Color.red); @@ -226,29 +335,23 @@ public class JamView extends JPanel int ypos = (getHeight() - scaleHeight) - ( Math.abs(thisRead.isize) ); int thisStart = drawRead(g2, thisRead, pixPerBase, stroke, ypos); - int nextStart = drawRead(g2, nextRead, pixPerBase, stroke, ypos); - g2.setStroke(originalStroke); - g2.setColor(Color.LIGHT_GRAY); - g2.drawLine(thisStart, ypos, nextStart, ypos); - } - else - i--; + int nextStart = drawRead(g2, nextRead, pixPerBase, stroke, ypos); + g2.setStroke(originalStroke); + g2.setColor(Color.LIGHT_GRAY); + g2.drawLine(thisStart, ypos, nextStart, ypos); + } + else + i--; } - } + } } - private int getBaseAtStartOfView() - { - String refName = (String) combo.getSelectedItem(); - int seqLength = seqLengths.get(refName); - double x = jspView.getViewport().getViewRect().getX(); - return (int) (seqLength * ( x / getWidth())); - } private void drawScale(Graphics2D g2, int start, int end, float pixPerBase) { g2.setColor(Color.black); - g2.drawLine( (int)(start*pixPerBase), getHeight()-14, (int)(end*pixPerBase), getHeight()-14); + g2.drawLine( (int)(start*pixPerBase), getHeight()-14, + (int)(end*pixPerBase), getHeight()-14); int interval = end-start; if(interval > 256000) @@ -261,11 +364,17 @@ public class JamView extends JPanel drawTicks(g2, start, end, pixPerBase, 800); else if(interval > 1000) drawTicks(g2, start, end, pixPerBase, 200); + else + drawTicks(g2, start, end, pixPerBase, 50); } private void drawTicks(Graphics2D g2, int start, int end, float pixPerBase, int division) { - int markStart = (Math.round(start/division)*division)+division; + int markStart = (Math.round(start/division)*division); + + if(markStart < 1) + markStart = 1; + int sm = markStart-(division/2); if(sm > start) @@ -280,25 +389,24 @@ public class JamView extends JPanel if(sm < end) g2.drawLine((int)(sm*pixPerBase), getHeight()-14,(int)(sm*pixPerBase), getHeight()-12); + + if(m == 1) + m = 0; } } private int drawRead(Graphics2D g2, Read read, - float pixPerBase, Stroke stroke, int ypos) + float pixPerBase, Stroke stroke, int ypos) { - int thisStart = (int) (read.pos*pixPerBase); + int thisStart = (int) ((read.pos-1)*pixPerBase); int thisEnd = (int) (thisStart + (read.seq.length()*pixPerBase)); g2.setStroke(stroke); g2.drawLine(thisStart, ypos, thisEnd, ypos); return thisStart; } - - - public void showJFrame() { - this.nbasesInView = nbasesInView; JFrame frame = new JFrame("JamTool"); combo = new JComboBox(seqNames); @@ -311,10 +419,10 @@ public class JamView extends JPanel } }); - frame.setPreferredSize(new Dimension(1000,800)); + frame.setPreferredSize(new Dimension(1000,500)); setLength(nbasesInView); - jspView = new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_NEVER, + jspView = new JScrollPane(this, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); JPanel panel = (JPanel) frame.getContentPane(); @@ -334,11 +442,15 @@ public class JamView extends JPanel switch(event.getKeyCode()) { case KeyEvent.VK_UP: + int startBase = getBaseAtStartOfView(); setZoomLevel( (int) (JamView.this.nbasesInView*1.1) ); + setViewToBasePosition(startBase); repaint(); break; case KeyEvent.VK_DOWN: + startBase = getBaseAtStartOfView(); setZoomLevel( (int) (JamView.this.nbasesInView*.9) ); + setViewToBasePosition(startBase); repaint(); break; default: @@ -356,37 +468,57 @@ public class JamView extends JPanel frame.setVisible(true); } + private int getBaseAtStartOfView() + { + String refName = (String) combo.getSelectedItem(); + int seqLength = seqLengths.get(refName); + double x = jspView.getViewport().getViewRect().getX(); + return (int) (seqLength * ( x / getWidth())); + } + + /** + * Set the panel size based on the number of bases visible + * and repaint. + * @param nbasesInView + */ private void setZoomLevel(final int nbasesInView) { - int startBase = getBaseAtStartOfView(); - this.nbasesInView = nbasesInView; setLength(this.nbasesInView); revalidate(); repaint(); - System.out.println(startBase+" "+getBaseAtStartOfView()); + } + + /** + * Set the ViewPort so it starts at the given base position. + * @param base + */ + private void setViewToBasePosition(int base) + { + Point p = jspView.getViewport().getViewPosition(); String refName = (String) combo.getSelectedItem(); int seqLength = seqLengths.get(refName); - - - float pixPerBase = ((float)getWidth())/(float)(seqLength); - Point p = jspView.getViewport().getViewPosition(); - p.x = (int)(startBase*pixPerBase); + p.x = (int) ((getPreferredSize().width)*(((float)base)/(float)seqLength)); jspView.getViewport().setViewPosition(p); - revalidate(); - repaint(); - - System.out.println(startBase+" "+getBaseAtStartOfView()); } + /** + * Set the panel size based on the number of bases visible. + * @param nbasesInView + */ private void setLength(int basesToShow) { String refName = (String) combo.getSelectedItem(); int seqLength = seqLengths.get(refName); - float pixPerBase = 1000.f/(float)(basesToShow); - setPreferredSize( new Dimension((int) (seqLength*pixPerBase), - getSize().height) ); + double pixPerBase = 1000.d/(double)(basesToShow); + + System.out.println(pixPerBase+" "+ALIGNMENT_PIX_PER_BASE); + if(pixPerBase > ALIGNMENT_PIX_PER_BASE) + pixPerBase = ALIGNMENT_PIX_PER_BASE; + Dimension d = new Dimension(); + d.setSize((seqLength*pixPerBase), 800.d); + setPreferredSize(d); } /** @@ -458,16 +590,67 @@ public class JamView extends JPanel return pr1.qname.compareTo(pr2.qname); } } + + public Dimension getPreferredScrollableViewportSize() + { + return getPreferredSize(); + } + + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) + { + if (orientation == SwingConstants.HORIZONTAL) + return visibleRect.width - maxUnitIncrement; + else + return visibleRect.height - maxUnitIncrement; + } + + public boolean getScrollableTracksViewportHeight() + { + return false; + } + + public boolean getScrollableTracksViewportWidth() + { + return false; + } + + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, + int direction) + { + //Get the current position. + int currentPosition = 0; + if (orientation == SwingConstants.HORIZONTAL) + currentPosition = visibleRect.x; + else + currentPosition = visibleRect.y; + + //Return the number of pixels between currentPosition + //and the nearest tick mark in the indicated direction. + if (direction < 0) + { + int newPosition = currentPosition - + (currentPosition / maxUnitIncrement) + * maxUnitIncrement; + return (newPosition == 0) ? maxUnitIncrement : newPosition; + } + else + { + return ((currentPosition / maxUnitIncrement) + 1) + * maxUnitIncrement + - currentPosition; + } + } public static void main(String[] args) { String bam = args[0]; int nbasesInView = Integer.parseInt(args[1]); String reference = null; - if(args.length > 2) - reference = args[2]; + //if(args.length > 2) + // reference = args[2]; - JamView view = new JamView(bam, reference, nbasesInView); - view.showJFrame(); + JamView view = new JamView(bam, reference, nbasesInView); + view.showJFrame(); } }