// Crossover applet // ---------------- // Educational demo program showing the relationship between recombination // fraction and map distance between two loci on a chromosome. // // Author: // Paul O. Lewis // Department of Ecology and // Evolutionary Biology // The University of Connecticut // 75 N. Eagleville Road, Unit 3043 // Storrs, CT 06269-3043 // // Tel: (860) 486-2069 // Fax: (860) 486-6364 // E-mail: paul.lewis@uconn.edu // http://hydrodictyon.eeb.uconn.edu/people/plewis/ // // Copyright (C) 1999 by Paul O. Lewis // // While now modified almost beyond recognition, this applet grew out of // playing with the Graph.java applet created by Jim Hagen and available // through the web site java.sun.com // // Permission to use, copy, modify, and distribute this software // and its documentation for NON-COMMERCIAL purposes and // without fee is hereby granted. import java.applet.Applet; import java.awt.event.*; import java.awt.*; import java.io.*; import java.util.*; import java.net.*; class Chromatid { Color color; int xleft; int xright; int y; int y1; int locusA; int locusB; String alleleA; String alleleB; Vector crossoverPoints; boolean myA; boolean myB; final int radius = 2; public Chromatid( Color c, int xl, int xr, int yme, int yother1, int xA, int xB, String allA, String allB ) { color = c; xleft = xl; xright = xr; y = yme; y1 = yother1; locusA = xA; locusB = xB; alleleA = allA; alleleB = allB; myA = true; myB = true; crossoverPoints = new Vector(2, 2); } public boolean IsMyA() { return myA; } public boolean IsMyB() { return myB; } public void SetLocusA( int k ) { locusA = k; } public void SetLocusB( int k ) { locusB = k; } public void ResetCrossOverPoints() { myA = true; myB = true; crossoverPoints.removeAllElements(); } public void AddCrossOverPoint( double x ) { Double d = new Double(x); crossoverPoints.addElement(d); } public void UpdateConstitutionFlags() { if( crossoverPoints.isEmpty() ) { myA = true; myB = true; } else { int crossovers = crossoverPoints.size(); int xstart = xleft; boolean atOtherY = false; for( int i = 0; i < crossovers; i++ ) { // xopoint is the position of the next crossover point // as a fraction of the chromatid's length Double d = (Double)crossoverPoints.elementAt(i); double xopoint = d.doubleValue(); int xend = xleft + (int)( xopoint * ( xright - xleft ) ); // ignore crossovers that are too close to previous crossover to draw if( xend < xstart + radius ) continue; // ignore crossovers if they are right on top of one of the loci int posA = xleft + locusA; int posB = xleft + locusB; if( (xend - radius <= posA) && (xend + radius >= posA) ) continue; if( (xend - radius <= posB) && (xend + radius >= posB) ) continue; if( atOtherY ) { // identify locus A if it lies in this section if( posA >= xstart && posA < xend - radius ) { myA = false; } // identify locus B if it lies in this section if( posB >= xstart && posB < xend - radius ) { myB = false; } atOtherY = false; } else { // identify locus A if it lies in this section if( posA >= xstart && posA < xend - radius ) { myA = true; } // identify locus B if it lies in this section if( posB >= xstart && posB < xend - radius ) { myB = true; } atOtherY = true; } xstart = xend + radius + 2; } if( atOtherY ) { // identify locus A if it lies in this section int posA = xleft + locusA; if( posA >= xstart && posA < xright ) { myA = false; } // identify locus B if it lies in this section int posB = xleft + locusB; if( posB >= xstart && posB < xright ) { myB = false; } } else { // identify locus A if it lies in this section int posA = xleft + locusA; if( posA >= xstart && posA < xright ) { myA = true; } // place locus B if it lies in this section int posB = xleft + locusB; if( posB >= xstart && posB < xright ) { myB = true; } } } } public void paint( Graphics g ) { g.setColor( color ); if( crossoverPoints.isEmpty() ) { g.drawLine( xleft, y, xright, y ); g.drawString( alleleA, xleft + locusA, y-radius ); g.drawString( alleleB, xleft + locusB, y-radius ); g.fillOval( xleft+locusA-radius, y-radius, 2*radius, 2*radius ); g.fillOval( xleft+locusB-radius, y-radius, 2*radius, 2*radius ); } else { int crossovers = crossoverPoints.size(); int xstart = xleft; boolean atOtherY = false; for( int i = 0; i < crossovers; i++ ) { // xopoint is the position of the next crossover point // as a fraction of the chromatid's length Double d = (Double)crossoverPoints.elementAt(i); double xopoint = d.doubleValue(); int xend = xleft + (int)( xopoint * ( xright - xleft ) ); // ignore crossovers that are too close to previous crossover to draw if( xend < xstart + radius ) continue; // ignore crossovers if they are right on top of one of the loci int posA = xleft + locusA; int posB = xleft + locusB; if( (xend - radius <= posA) && (xend + radius >= posA) ) continue; if( (xend - radius <= posB) && (xend + radius >= posB) ) continue; if( atOtherY ) { g.drawLine( xstart, y1, xend - radius, y1 ); g.drawLine( xend - radius, y1, xend + 2*radius, y ); // identify locus A if it lies in this section if( posA >= xstart && posA < xend - radius ) { g.drawString( alleleA, xleft + locusA, y1 - radius ); g.fillOval( xleft + locusA - radius, y1 - radius, 2*radius, 2*radius ); } // identify locus B if it lies in this section if( posB >= xstart && posB < xend - radius ) { g.drawString( alleleB, xleft + locusB, y1 - radius ); g.fillOval( xleft + locusB - radius, y1 - radius, 2*radius, 2*radius ); } atOtherY = false; } else { g.drawLine( xstart, y, xend - radius, y ); g.drawLine( xend - radius, y, xend + 2*radius, y1 ); // identify locus A if it lies in this section if( posA >= xstart && posA < xend - radius ) { g.drawString( alleleA, xleft + locusA, y - radius ); g.fillOval( xleft + locusA - radius, y - radius, 2*radius, 2*radius ); } // identify locus B if it lies in this section if( posB >= xstart && posB < xend - radius ) { g.drawString( alleleB, xleft + locusB, y - radius ); g.fillOval( xleft + locusB - radius, y - radius, 2*radius, 2*radius ); } atOtherY = true; } xstart = xend + radius + 2; } if( atOtherY ) { g.drawLine( xstart, y1, xright, y1 ); // identify locus A if it lies in this section int posA = xleft + locusA; if( posA >= xstart && posA < xright ) { g.drawString( alleleA, xleft + locusA, y1 - radius ); g.fillOval( xleft + locusA - radius, y1 - radius, 2*radius, 2*radius ); } // identify locus B if it lies in this section int posB = xleft + locusB; if( posB >= xstart && posB < xright ) { g.drawString( alleleB, xleft + locusB, y1 - radius ); g.fillOval( xleft + locusB - radius, y1 - radius, 2*radius, 2*radius ); } } else { g.drawLine( xstart, y, xright, y ); // identify locus A if it lies in this section int posA = xleft + locusA; if( posA >= xstart && posA < xright ) { g.drawString( alleleA, xleft + locusA, y - radius ); g.fillOval( xleft + locusA - radius, y - radius, 2*radius, 2*radius ); } // place locus B if it lies in this section int posB = xleft + locusB; if( posB >= xstart && posB < xright ) { g.drawString( alleleB, xleft + locusB, y - radius ); g.fillOval( xleft + locusB - radius, y - radius, 2*radius, 2*radius ); } } } } }; class ChromoPanel extends Canvas implements MouseListener, MouseMotionListener { final int width = 200; final int height = 200; Chromatid one_a; Chromatid one_b; Chromatid two_a; Chromatid two_b; int nAB; int nab; int nAb; int naB; double locusA = 0.3; double locusB = 0.8; Font normalFont = null; Image offimg = null; Graphics offg = null; boolean first_update = true; public ChromoPanel() { setSize( width, height ); // create chromatids, specifying // color of chromatid // x coordinate of left end of chromatid // x coordinate of right end of chromatid // y coordinate of chromatid // y coordinate of one non-homologous chromatid // x coordinate of the A locus // x coordinate of the B locus int xleft = 10; int xright = width - 10; int y = 2*height/8; int yother = 5*height/8; int xA = (int)(locusA * (double)( width - 20 ) ); int xB = (int)(locusB * (double)( width - 20 ) ); one_a = new Chromatid( Color.blue, xleft, xright, y, yother, xA, xB, "A", "B" ); xleft = 10; xright = width - 10; y = 3*height/8; yother = 6*height/8; one_b = new Chromatid( Color.blue, xleft, xright, y, yother, xA, xB, "A", "B" ); xleft = 10; xright = width - 10; y = 5*height/8; yother = 2*height/8; two_a = new Chromatid( Color.red, xleft, xright, y, yother, xA, xB, "a", "b" ); xleft = 10; xright = width - 10; y = 6*height/8; yother = 3*height/8; two_b = new Chromatid( Color.red, xleft, xright, y, yother, xA, xB, "a", "b" ); nAB = 0; nab = 0; nAb = 0; naB = 0; } public void ResetCounts() { nAB = 0; nab = 0; nAb = 0; naB = 0; one_a.ResetCrossOverPoints(); one_b.ResetCrossOverPoints(); two_a.ResetCrossOverPoints(); two_b.ResetCrossOverPoints(); repaint(); } public void GetCounts( Label AB, Label ab, Label Ab, Label aB, Label rf ) { AB.setText( Integer.toString(nAB) ); ab.setText( Integer.toString(nab) ); Ab.setText( Integer.toString(nAb) ); aB.setText( Integer.toString(naB) ); double d = 0.0; double total = (double)( nAB + nab + nAb + naB ); if( total > 0.0 ) { d = (double)( nAb + naB ) / total; rf.setText( Double.toString(d) ); } else rf.setText( "*****" ); } public void SetFirstLocusPosition( double d ) { int xA = (int)(d * (double)( width - 20 ) ); one_a.SetLocusA(xA); one_b.SetLocusA(xA); two_a.SetLocusA(xA); two_b.SetLocusA(xA); repaint(); } public void SetSecondLocusPosition( double d ) { int xB = (int)(d * (double)( width - 20 ) ); one_a.SetLocusB(xB); one_b.SetLocusB(xB); two_a.SetLocusB(xB); two_b.SetLocusB(xB); repaint(); } public void UpdateCounts() { one_a.UpdateConstitutionFlags(); one_b.UpdateConstitutionFlags(); two_a.UpdateConstitutionFlags(); two_b.UpdateConstitutionFlags(); if( one_a.IsMyA() && one_a.IsMyB() ) { nAB++; } else if( one_a.IsMyA() && !one_a.IsMyB() ) { nAb++; } else if( !one_a.IsMyA() && one_a.IsMyB() ) { naB++; } else if( !one_a.IsMyA() && !one_a.IsMyB() ) { nab++; } if( one_b.IsMyA() && one_b.IsMyB() ) { nAB++; } else if( one_b.IsMyA() && !one_b.IsMyB() ) { nAb++; } else if( !one_b.IsMyA() && one_b.IsMyB() ) { naB++; } else if( !one_b.IsMyA() && !one_b.IsMyB() ) { nab++; } if( two_a.IsMyA() && two_a.IsMyB() ) nab++; else if( two_a.IsMyA() && !two_a.IsMyB() ) naB++; else if( !two_a.IsMyA() && two_a.IsMyB() ) nAb++; else if( !two_a.IsMyA() && !two_a.IsMyB() ) nAB++; if( two_b.IsMyA() && two_b.IsMyB() ) nab++; else if( two_b.IsMyA() && !two_b.IsMyB() ) naB++; else if( !two_b.IsMyA() && two_b.IsMyB() ) nAb++; else if( !two_b.IsMyA() && !two_b.IsMyB() ) nAB++; } public double GetNextExponential( Random rng, double lambda ) { // pick an exponential random variable based on lambda double exprn = 0.0; while( exprn <= 0.0 || exprn > 1.0 ) exprn = 1.0 - rng.nextDouble(); exprn = -Math.log(exprn); // was dividing by lambda here; return exprn; } public void Simulate( Random rng, double lambda ) { // pick exponential random variables to determine // crossover points double currPos1 = 0.0; // for chromatids one_a and two_a double currPos2 = 0.0; // for chromatids one_b and two_b // resetting counts each time for testing purposes //ResetCounts(); // counts accumulate with each simulation (normal procedure) one_a.ResetCrossOverPoints(); one_b.ResetCrossOverPoints(); two_a.ResetCrossOverPoints(); two_b.ResetCrossOverPoints(); for(;;) { double exprn = GetNextExponential( rng, lambda ); // place a crossover at a distance exprn from // left end of chromatids one_a and two_a currPos1 += exprn; if( currPos1 >= 1.0 ) break; one_a.AddCrossOverPoint( currPos1 ); two_a.AddCrossOverPoint( currPos1 ); exprn = GetNextExponential( rng, lambda ); // place a crossover at a distance exprn from // left end of chromatids one_b and two_b currPos2 += exprn; if( currPos2 >= 1.0 ) break; one_b.AddCrossOverPoint( currPos2 ); two_b.AddCrossOverPoint( currPos2 ); } repaint(); } public void update( Graphics g ) { if( first_update ) { offimg = createImage( width, height ); offg = offimg.getGraphics(); normalFont = new Font( "Ariel", Font.BOLD, 10 ); first_update = false; } offg.setColor( Color.lightGray ); offg.fillRect( 0, 0, width, height ); // paint chromosomes one_a.paint(offg); one_b.paint(offg); two_a.paint(offg); two_b.paint(offg); paint(g); } public void paint( Graphics g ) { if( first_update ) repaint(); else g.drawImage( offimg, 0, 0, this ); } public Dimension getPreferredSize() { return new Dimension( width, height ); } public void mouseMoved( MouseEvent e ) {} public void mouseEntered( MouseEvent e ) {} public void mouseExited( MouseEvent e ) {} public void mouseClicked( MouseEvent e ) {} public void mousePressed( MouseEvent e ) { int curr_x = e.getX(); int curr_y = e.getY(); // do stuff } public void mouseReleased( MouseEvent e ) { int curr_x = e.getX(); int curr_y = e.getY(); // do stuff repaint(); } public void mouseDragged( MouseEvent e ) { int curr_x = e.getX(); int curr_y = e.getY(); // do stuff repaint(); } }; public class CrossOver extends Applet implements WindowListener, ActionListener, ItemListener, MouseListener, MouseMotionListener { Label num_AB; Label num_ab; Label num_Ab; Label num_aB; Label recomb_frac; ChromoPanel chromoPanel = null; Button simButton = null; Button resetButton = null; Choice centiMorgans = null; Choice firstLocus = null; Choice secondLocus = null; boolean first_update = true; Random rng; Point locusA1; Point locusA2; Point locusB1; Point locusB2; public CrossOver() { rng = new Random(); locusA1 = new Point(); locusA2 = new Point(); locusB1 = new Point(); locusB2 = new Point(); } public void setAppletFrame( Frame f ) { f.addWindowListener(this); } public void windowOpened( WindowEvent e ) {} public void windowClosing( WindowEvent e ) { System.exit(0); } public void windowClosed( WindowEvent e ) {} public void windowIconified( WindowEvent e ) {} public void windowDeiconified( WindowEvent e ) {} public void windowActivated( WindowEvent e ) {} public void windowDeactivated( WindowEvent e ) {} public void actionPerformed( ActionEvent e ) { if( e.getSource() == simButton ) { Simulate(); } else if( e.getSource() == resetButton ) { chromoPanel.ResetCounts(); chromoPanel.GetCounts( num_AB, num_ab, num_Ab, num_aB, recomb_frac ); } } public void itemStateChanged( ItemEvent e ) { if( e.getSource() == firstLocus ) { double d = Double.valueOf( firstLocus.getSelectedItem() ).doubleValue(); chromoPanel.SetFirstLocusPosition(d); } else if( e.getSource() == secondLocus ) { double d = Double.valueOf( secondLocus.getSelectedItem() ).doubleValue(); chromoPanel.SetSecondLocusPosition(d); } } public void mouseClicked( MouseEvent e ) {} public void mousePressed( MouseEvent e ) { Point clickPoint = e.getPoint(); } public void mouseReleased( MouseEvent e ) { // do stuff } public void mouseEntered( MouseEvent e ) {} public void mouseExited( MouseEvent e ) {} public void mouseDragged( MouseEvent e ) {} public void mouseMoved( MouseEvent e ) {} public void Simulate() { // compute lambda from centiMorgans Choice object String ss = centiMorgans.getSelectedItem(); double cM = Double.valueOf(ss).doubleValue(); double lambda = cM / 100.0; chromoPanel.Simulate( rng, lambda ); chromoPanel.UpdateCounts(); chromoPanel.GetCounts( num_AB, num_ab, num_Ab, num_aB, recomb_frac ); } public void init() { setLayout( new BorderLayout() ); Panel p1 = new Panel(); chromoPanel = new ChromoPanel(); chromoPanel.addMouseListener(this); chromoPanel.addMouseListener(chromoPanel); chromoPanel.addMouseMotionListener(this); chromoPanel.addMouseMotionListener(chromoPanel); p1.add(chromoPanel); add( "Center", p1 ); Panel p2 = new Panel(); simButton = new Button( "Simulate" ); simButton.addActionListener(this); p2.add(simButton); resetButton = new Button( "Reset" ); resetButton.addActionListener(this); p2.add(resetButton); add( "South", p2 ); Panel p3 = new Panel(); p3.setLayout( new GridLayout( 8, 1 ) ); // centimorgans choice centiMorgans = new Choice(); centiMorgans.addItem( "100" ); centiMorgans.addItem( "200" ); centiMorgans.addItem( "500" ); centiMorgans.addItem( "1000" ); Panel p3a = new Panel(); p3a.add( new Label( "centimorgans:", Label.RIGHT ) ); p3a.add( centiMorgans ); p3.add(p3a); // firstLocus choice firstLocus = new Choice(); firstLocus.addItem( "0.1" ); firstLocus.addItem( "0.2" ); firstLocus.addItem( "0.3" ); firstLocus.addItem( "0.4" ); firstLocus.addItem( "0.5" ); firstLocus.addItem( "0.6" ); firstLocus.addItem( "0.7" ); firstLocus.addItem( "0.8" ); firstLocus.addItem( "0.9" ); firstLocus.select(2); firstLocus.addItemListener(this); Panel p3b = new Panel(); p3b.add( new Label( "locus A:", Label.RIGHT ) ); p3b.add( firstLocus ); p3.add(p3b); // secondLocus choice secondLocus = new Choice(); secondLocus.addItem( "0.1" ); secondLocus.addItem( "0.2" ); secondLocus.addItem( "0.3" ); secondLocus.addItem( "0.4" ); secondLocus.addItem( "0.5" ); secondLocus.addItem( "0.6" ); secondLocus.addItem( "0.7" ); secondLocus.addItem( "0.8" ); secondLocus.addItem( "0.9" ); secondLocus.select(7); secondLocus.addItemListener(this); Panel p3c = new Panel(); p3c.add( new Label( "locus B:", Label.RIGHT ) ); p3c.add( secondLocus ); p3.add(p3c); Panel p3d = new Panel(); p3d.add( new Label( "No. AB gametes:", Label.RIGHT ) ); num_AB = new Label( "0", Label.LEFT ); p3d.add( num_AB ); p3.add(p3d); Panel p3e = new Panel(); p3e.add( new Label( "No. ab gametes:", Label.RIGHT ) ); num_ab = new Label( "0", Label.LEFT ); p3e.add( num_ab ); p3.add(p3e); Panel p3f = new Panel(); p3f.add( new Label( "No. Ab gametes:", Label.RIGHT ) ); num_Ab = new Label( "0", Label.LEFT ); p3f.add( num_Ab ); p3.add(p3f); Panel p3g = new Panel(); p3g.add( new Label( "No. aB gametes:", Label.RIGHT ) ); num_aB = new Label( "0", Label.LEFT ); p3g.add( num_aB ); p3.add(p3g); Panel p3h = new Panel(); p3h.add( new Label( "Recomb. fraction:", Label.RIGHT ) ); recomb_frac = new Label( "*****", Label.LEFT ); p3h.add( recomb_frac ); p3.add(p3h); add( "West", p3 ); repaint(); } public static void main( String args[] ) { Frame f = new Frame("Crossing Over Demo"); f.setSize( 500, 300 ); CrossOver xo = new CrossOver(); xo.setAppletFrame(f); xo.init(); f.add( "Center", xo ); f.show(); } }