/* VARNA is a tool for the automated drawing, visualization and annotation of the secondary structure of RNA, designed as a companion software for web servers and databases. Copyright (C) 2008 Kevin Darty, Alain Denise and Yann Ponty. electronic mail : Yann.Ponty@lri.fr paper mail : LRI, bat 490 Université Paris-Sud 91405 Orsay Cedex France This file is part of VARNA version 3.1. VARNA version 3.1 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. VARNA version 3.1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with VARNA version 3.1. If not, see http://www.gnu.org/licenses. */ package fr.orsay.lri.varna.models.rna; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Point; import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Reader; import java.io.Serializable; import java.io.StreamTokenizer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.Vector; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import statalign.postprocess.gui.ColorGradient; import statalign.postprocess.gui.StructureGUI; import statalign.postprocess.plugins.Structure; import fr.orsay.lri.varna.exceptions.ExceptionExportFailed; import fr.orsay.lri.varna.exceptions.ExceptionFileFormatOrSyntax; import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate; import fr.orsay.lri.varna.exceptions.ExceptionLoadingFailed; import fr.orsay.lri.varna.exceptions.ExceptionNAViewAlgorithm; import fr.orsay.lri.varna.exceptions.ExceptionPermissionDenied; import fr.orsay.lri.varna.exceptions.ExceptionUnmatchedClosingParentheses; import fr.orsay.lri.varna.exceptions.ExceptionWritingForbidden; import fr.orsay.lri.varna.interfaces.InterfaceVARNAListener; import fr.orsay.lri.varna.interfaces.InterfaceVARNAObservable; import fr.orsay.lri.varna.models.CubicBezierCurve; import fr.orsay.lri.varna.models.VARNAConfig; import fr.orsay.lri.varna.models.annotations.ChemProbAnnotation; import fr.orsay.lri.varna.models.annotations.HighlightRegionAnnotation; import fr.orsay.lri.varna.models.annotations.TextAnnotation; import fr.orsay.lri.varna.models.export.PSExport; import fr.orsay.lri.varna.models.export.SVGExport; import fr.orsay.lri.varna.models.export.SecStrDrawingProducer; import fr.orsay.lri.varna.models.export.XFIGExport; import fr.orsay.lri.varna.models.naView.NAView; import fr.orsay.lri.varna.models.templates.RNANodeValue2TemplateDistance; import fr.orsay.lri.varna.models.templates.RNANodeValueTemplate; import fr.orsay.lri.varna.models.templates.RNANodeValueTemplateBasePair; import fr.orsay.lri.varna.models.templates.RNATemplate; import fr.orsay.lri.varna.models.templates.RNATemplateAlign; import fr.orsay.lri.varna.models.templates.RNATemplateDrawingAlgorithmException; import fr.orsay.lri.varna.models.templates.RNATemplateMapping; import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition; import fr.orsay.lri.varna.models.templates.RNATemplate.In1Is; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence; import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint; import fr.orsay.lri.varna.models.treealign.RNANodeValue2; import fr.orsay.lri.varna.models.treealign.RNATree2; import fr.orsay.lri.varna.models.treealign.RNATree2Exception; import fr.orsay.lri.varna.models.treealign.Tree; import fr.orsay.lri.varna.models.treealign.TreeAlign; import fr.orsay.lri.varna.models.treealign.TreeAlignException; import fr.orsay.lri.varna.models.treealign.TreeAlignResult; import fr.orsay.lri.varna.views.VueUI; import java.lang.Math; /** * The RNA model which contain the base list and the draw algorithm mode * * @author darty * */ public class RNA extends InterfaceVARNAObservable implements Serializable{ /** * */ private static final long serialVersionUID = 7541274455751497303L; /** * Selects the "Feynman diagram" drawing algorithm that places the bases on * a circle and draws the base-pairings as chords of the circle graph. */ public static final int DRAW_MODE_CIRCULAR = 1; /** * Selects the "tree drawing" algorithm. Draws each loop on a circle whose * radius depends on the number of bases involved in the loop. As some * helices can be overlapping in the result, basic interaction is provided * so that the user can "disentangle" the drawing by spinning the helices * around the axis defined by their multiloop (bulge or internal loop) * origin. This is roughly the initial placement strategy of RNAViz. * * @see <a href="http://rnaviz.sourceforge.net/">RNAViz</a> */ public static final int DRAW_MODE_RADIATE = 2; /** * Selects the NAView algorithm. */ public static final int DRAW_MODE_NAVIEW = 3; /** * Selects the linear algorithm. */ public static final int DRAW_MODE_LINEAR = 4; public static final int DRAW_MODE_VARNA_VIEW = 5; /** * Selects the RNAView algorithm. */ public static final int DRAW_MODE_MOTIFVIEW = 6; public static final int DRAW_MODE_TEMPLATE = 7; public static final int DEFAULT_DRAW_MODE = DRAW_MODE_RADIATE; private Double _spaceBetweenBases = 1.0; /** * The draw algorithm mode */ private int _drawMode = DRAW_MODE_RADIATE; public int BASE_RADIUS = 10; public static final double LOOP_DISTANCE = 40.0; // distance between base pairs in an helix public static final double BASE_PAIR_DISTANCE = 65.0; // distance between the two bases of a pair public static final double MULTILOOP_DISTANCE = 35.0; public static final double VIRTUAL_LOOP_RADIUS= 40.0; public double CHEM_PROB_DIST = 14; public double CHEM_PROB_BASE_LENGTH = 30; public double CHEM_PROB_ARROW_HEIGHT = 10; public double CHEM_PROB_ARROW_WIDTH = 5; public double CHEM_PROB_TRIANGLE_WIDTH = 2.5; public double CHEM_PROB_PIN_SEMIDIAG = 6; public double CHEM_PROB_DOT_RADIUS = 6.; public GeneralPath _debugShape = null; private boolean _drawn = false; public static boolean probMode = true; private ColorGradient cg = new ColorGradient(Color.WHITE, Color.BLUE); public double _bpHeightIncrement = VARNAConfig.DEFAULT_BP_INCREMENT; /** * the base list */ protected ArrayList<ModeleBase> _listeBases; /** * the strand list */ StructureTemp _listStrands = new StructureTemp(); /** * Additional bonds and info can be specified here. */ private ArrayList<ModeleBP> _structureAux = new ArrayList<ModeleBP>(); transient private ArrayList<InterfaceVARNAListener> _listeVARNAListener = new ArrayList<InterfaceVARNAListener>(); static ArrayList<String> _normalBases = new ArrayList<String>(); { _normalBases.add("a"); _normalBases.add("c"); _normalBases.add("g"); _normalBases.add("u"); _normalBases.add("t"); } private ArrayList<TextAnnotation> _listeAnnotations = new ArrayList<TextAnnotation>(); private ArrayList<HighlightRegionAnnotation> _listeRegionHighlights = new ArrayList<HighlightRegionAnnotation>(); private String _name = ""; public RNA() { this(""); } public RNA(String n) { _name = n; _listeBases = new ArrayList<ModeleBase>(); _drawn = false; init(); } public String toString() { if (_name.equals("")) { return getStructDBN(); } else { return _name; } } public RNA(RNA r) { _spaceBetweenBases = r._spaceBetweenBases; _drawMode = r._drawMode; _listeBases.addAll(r._listeBases); _listeVARNAListener = (ArrayList<InterfaceVARNAListener>) r._listeVARNAListener; _drawn = r._drawn; init(); } public void init() { } public void saveRNADBN(String path, String title) throws ExceptionWritingForbidden { try { FileWriter out = new FileWriter(path); if (!title.equals("")) { out.write("> " + title + "\n"); } out.write(getListeBasesToString()); out.write('\n'); String str = ""; for (int i = 0; i < _listeBases.size(); i++) { if (_listeBases.get(i).getElementStructure() == -1) { str += '.'; } else { if (_listeBases.get(i).getElementStructure() > i) { str += '('; } else { str += ')'; } } } out.write(str); out.write('\n'); out.close(); } catch (IOException e) { throw new ExceptionWritingForbidden(e.getMessage()); } } public Color getBaseInnerColor(int i, VARNAConfig conf) { Color result = _listeBases.get(i).getStyleBase() .get_base_inner_color(); String res = _listeBases.get(i).getContent(); if(!probMode) { // GUI displayed normal mode. if(res.toLowerCase().equals("a")) {result = Color.RED;} else if(res.toLowerCase().equals("c")) {result = Color.BLUE;} else if(res.toLowerCase().equals("g")) {result = Color.YELLOW;} else if(res.toLowerCase().equals("u") || res.toLowerCase().equals("t")) {result = Color.GREEN;} else if ((conf._colorSpecialBases && !_normalBases.contains(res.toLowerCase()))) { result = conf._specialBasesColor; } } else { // GUI displayed is base-pairing probability mode. ColorGradient cg; // Color gradient to weight coloring based on float probabilities /* If a nucleotide is base-paired with another nucleotide with some * probability, use color gradient to paint nucleotide blue weighted with the base-pairing * probability. */ if(Structure.currentDotBracketStructure.charAt(i) == '(') { cg = new ColorGradient(Color.WHITE, Color.BLUE); int j = _listeBases.get(i).getStyleBP().getIndex3(); result = cg.getColor(Structure.probMatrix[i][j]); } else if(Structure.currentDotBracketStructure.charAt(i) == ')') { cg = new ColorGradient(Color.WHITE, Color.BLUE); int j = _listeBases.get(i).getStyleBP().getIndex5(); result = cg.getColor(Structure.probMatrix[i][j]); } /* * Else if the nucleotide is single, paint it red weighted with the probability * that the nucleotide is not base-paired. */ else if(Structure.currentDotBracketStructure.charAt(i) == '.') { cg = new ColorGradient(Color.WHITE, Color.RED); result = cg.getColor(Structure.singleMatrix[i]); } } return result; } public Color getBaseOuterColor(int i, VARNAConfig conf) { Color result = _listeBases.get(i).getStyleBase() .get_base_outline_color(); return result; } public Color getBaseNameColor(int i, VARNAConfig conf) { Color result = _listeBases.get(i).getStyleBase().get_base_name_color(); return result; } public Color getBasePairColor(ModeleBP bp, VARNAConfig conf) { Color bondColor = conf._bondColor; if (conf._useBaseColorsForBPs) { bondColor = _listeBases.get(bp.getPartner5().getIndex()) .getStyleBase().get_base_inner_color(); } if (bp != null) { bondColor = bp.getStyle().getColor(bondColor); } return bondColor; } public double getBasePairThickness(ModeleBP bp, VARNAConfig conf) { double thickness = bp.getStyle().getThickness(conf._bpThickness); return thickness; } private void drawSymbol(SecStrDrawingProducer out, double posx, double posy, double normx, double normy, double radius, boolean isCIS, ModeleBP.Edge e, double thickness) { Color bck = out.getCurrentColor(); switch (e) { case WATSON_CRICK: if (isCIS) { out.fillCircle(posx, posy, (radius / 2.0), thickness, bck); } else { out.drawCircle(posx, posy, (radius / 2.0), thickness); out.fillCircle(posx, posy, (radius / 2.0), thickness, Color.white); } break; case HOOGSTEEN: { double xtab[] = new double[4]; double ytab[] = new double[4]; xtab[0] = posx - radius * normx / 2.0 - radius * normy / 2.0; ytab[0] = posy - radius * normy / 2.0 + radius * normx / 2.0; xtab[1] = posx + radius * normx / 2.0 - radius * normy / 2.0; ytab[1] = posy + radius * normy / 2.0 + radius * normx / 2.0; xtab[2] = posx + radius * normx / 2.0 + radius * normy / 2.0; ytab[2] = posy + radius * normy / 2.0 - radius * normx / 2.0; xtab[3] = posx - radius * normx / 2.0 + radius * normy / 2.0; ytab[3] = posy - radius * normy / 2.0 - radius * normx / 2.0; if (isCIS) { out.fillPolygon(xtab, ytab, bck); } else { out.drawPolygon(xtab, ytab, thickness); out.fillPolygon(xtab, ytab, Color.white); } } break; case SUGAR: { double ix = radius * normx / 2.0; double iy = radius * normy / 2.0; double jx = radius * normy / 2.0; double jy = -radius * normx / 2.0; double xtab[] = new double[3]; double ytab[] = new double[3]; xtab[0] = posx - ix + jx; ytab[0] = posy - iy + jy; xtab[1] = posx + ix + jx; ytab[1] = posy + iy + jy; xtab[2] = posx - jx; ytab[2] = posy - jy; if (isCIS) { out.fillPolygon(xtab, ytab, bck); } else { out.drawPolygon(xtab, ytab, thickness); out.fillPolygon(xtab, ytab, Color.white); } } break; } } private void drawBasePair(SecStrDrawingProducer out, Point2D.Double orig, Point2D.Double dest, ModeleBP style, VARNAConfig conf) { double dx = dest.x - orig.x; double dy = dest.y - orig.y; double dist = Math.sqrt((dest.x - orig.x) * (dest.x - orig.x) + (dest.y - orig.y) * (dest.y - orig.y)); dx /= dist; dy /= dist; double nx = -dy; double ny = dx; orig = new Point2D.Double(orig.x+BASE_RADIUS*dx,orig.y+BASE_RADIUS*dy); dest = new Point2D.Double(dest.x-BASE_RADIUS*dx,dest.y-BASE_RADIUS*dy); if (conf._mainBPStyle == VARNAConfig.BP_STYLE.BP_STYLE_LW) { double thickness = getBasePairThickness(style, conf); double radiusCircle = ((BASE_PAIR_DISTANCE - BASE_RADIUS) / 5.0); if (style.isCanonical()) { if (style.isCanonicalGC()) { if ((orig.x != dest.x) || (orig.y != dest.y)) { nx *= BASE_RADIUS / 4.0; ny *= BASE_RADIUS / 4.0; out .drawLine((orig.x + nx), (orig.y + ny), (dest.x + nx), (dest.y + ny), conf._bpThickness); out .drawLine((orig.x - nx), (orig.y - ny), (dest.x - nx), (dest.y - ny), conf._bpThickness); } } else if (!style.isWobbleUG()) { double cx = (dest.x + orig.x) / 2.0; double cy = (dest.y + orig.y) / 2.0; out.drawLine(orig.x, orig.y, dest.x, dest.y, conf._bpThickness); drawSymbol(out, cx, cy, nx, ny, radiusCircle, style.isCIS(), style.getEdgePartner5(), thickness); } else { out.drawLine(orig.x, orig.y, dest.x, dest.y, conf._bpThickness); } } else { ModeleBP.Edge p1 = style.getEdgePartner5(); ModeleBP.Edge p2 = style.getEdgePartner3(); double cx = (dest.x + orig.x) / 2.0; double cy = (dest.y + orig.y) / 2.0; out.drawLine(orig.x, orig.y, dest.x, dest.y, conf._bpThickness); if (p1 == p2) { drawSymbol(out, cx, cy, nx, ny, radiusCircle, style.isCIS(), p1, thickness); } else { double vdx = (dest.x - orig.x); double vdy = (dest.y - orig.y); vdx /= 6.0; vdy /= 6.0; drawSymbol(out, cx + vdx, cy + vdy, nx, ny, radiusCircle, style.isCIS(), p2, thickness); drawSymbol(out, cx - vdx, cy - vdy, nx, ny, radiusCircle, style.isCIS(), p1, thickness); } } } else if (conf._mainBPStyle == VARNAConfig.BP_STYLE.BP_STYLE_RNAVIZ) { double xcenter = (orig.x + dest.x) / 2.0; double ycenter = (orig.y + dest.y) / 2.0; out.fillCircle(xcenter, ycenter, 3.0 * conf._bpThickness, conf._bpThickness, out.getCurrentColor()); } else if (conf._mainBPStyle == VARNAConfig.BP_STYLE.BP_STYLE_SIMPLE) { } } private void drawColorMap(VARNAConfig _conf,SecStrDrawingProducer out) { double v1 = _conf._cm.getMinValue(); double v2 = _conf._cm.getMaxValue(); int x,y; double xSpaceAvail = 0; double ySpaceAvail = 0; double thickness = 1.0; /*ySpaceAvail = Math.min((getHeight()-rnabbox.height*scaleFactor-getTitleHeight())/2.0,scaleFactor*(_conf._colorMapHeight+VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE)); if ((int)ySpaceAvail==0) { xSpaceAvail = Math.min((getWidth()-rnabbox.width*scaleFactor)/2,scaleFactor*(_conf._colorMapWidth)+VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH); }*/ Rectangle2D.Double currentBBox = out.getBoundingBox(); double xBase = (currentBBox.getMaxX() -_conf._colorMapWidth-_conf._colorMapXOffset); //double yBase = (minY - _conf._colorMapHeight + _conf._colorMapYOffset); double yBase = (currentBBox.getMinY()-_conf._colorMapHeight-VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE); for (int i=0;i<_conf._colorMapWidth;i++) { double ratio = (((double)i)/((double)_conf._colorMapWidth-1)); double val = v1+(v2-v1)*ratio; Color c = _conf._cm.getColorForValue(val); x = (int) (xBase + i); y = (int) yBase; out.fillRectangle(x, y, VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH, _conf._colorMapHeight,c); } out.setColor(VARNAConfig.DEFAULT_COLOR_MAP_OUTLINE); out.drawRectangle(xBase,yBase, (double)_conf._colorMapWidth+VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH-1, _conf._colorMapHeight,thickness); out.setColor(VARNAConfig.DEFAULT_COLOR_MAP_FONT_COLOR); out.setFont(out.getCurrentFont(),VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE/1.5); out.drawText(xBase, yBase+_conf._colorMapHeight+VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE/1.7, ""+_conf._cm.getMinValue()); out.drawText(xBase+VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH+_conf._colorMapWidth, yBase+_conf._colorMapHeight+VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE/1.7, ""+_conf._cm.getMaxValue()); out.drawText(xBase+(VARNAConfig.DEFAULT_COLOR_MAP_STRIPE_WIDTH+_conf._colorMapWidth)/2.0, yBase-(VARNAConfig.DEFAULT_COLOR_MAP_FONT_SIZE/1.7), _conf._colorMapCaption); } private void renderRegionHighlights(SecStrDrawingProducer out, Point2D.Double[] realCoords,Point2D.Double[] realCenters) { for (HighlightRegionAnnotation r:_listeRegionHighlights) { GeneralPath s = r.getShape(realCoords,realCenters,1.0); out.setColor(r.getFillColor()); out.fillPolygon(s, r.getFillColor()); out.setColor(r.getOutlineColor()); out.drawPolygon(s, 1l); } } private void saveRNA(String path, VARNAConfig conf, double scale, SecStrDrawingProducer out) throws ExceptionWritingForbidden { out.setScale(scale); // Computing bounding boxes double EPSMargin = 40; double minX = Double.MAX_VALUE; double maxX = Double.MIN_VALUE; double minY = Double.MAX_VALUE; double maxY = Double.MIN_VALUE; double x0, y0, x1, y1, xc, yc, xp, yp, dx, dy, norm; for (int i = 0; i < _listeBases.size(); i++) { minX = Math.min(minX, (_listeBases.get(i).getCoords().getX() - BASE_RADIUS - EPSMargin)); minY = Math.min(minY, -(_listeBases.get(i).getCoords().getY() - BASE_RADIUS - EPSMargin)); maxX = Math.max(maxX, (_listeBases.get(i).getCoords().getX() + BASE_RADIUS + EPSMargin)); maxY = Math.max(maxY, -(_listeBases.get(i).getCoords().getY() + BASE_RADIUS + EPSMargin)); } // Rescaling everything Point2D.Double[] coords = new Point2D.Double[_listeBases.size()]; Point2D.Double[] centers = new Point2D.Double[_listeBases.size()]; for (int i = 0; i < _listeBases.size(); i++) { xp = (_listeBases.get(i).getCoords().getX() - minX); yp = -(_listeBases.get(i).getCoords().getY() - minY); coords[i] = new Point2D.Double(xp,yp); Point2D.Double centerBck = getCenter(i); if (get_drawMode() == RNA.DRAW_MODE_NAVIEW || get_drawMode() == RNA.DRAW_MODE_RADIATE) { if ((_listeBases.get(i).getElementStructure() != -1) && i < _listeBases.size() - 1 && i > 1) { ModeleBase b1 = get_listeBases().get(i - 1); ModeleBase b2 = get_listeBases().get(i + 1); int j1 = b1.getElementStructure(); int j2 = b2.getElementStructure(); if ((j1==-1)^(j2==-1)) { // alors la position du nombre associé doit etre décalé Point2D.Double a1 = b1.getCoords(); Point2D.Double a2 = b2.getCoords(); Point2D.Double c1 = b1.getCenter(); Point2D.Double c2 = b2.getCenter(); centerBck.x = _listeBases.get(i).getCoords().x + (c1.x-a1.x)/c1.distance(a1)+(c2.x-a2.x)/c2.distance(a2); centerBck.y = _listeBases.get(i).getCoords().y + (c1.y-a1.y)/c1.distance(a1)+(c2.y-a2.y)/c2.distance(a2); } } } xc = (centerBck.getX() - minX); yc = -(centerBck.getY() - minY); centers[i] = new Point2D.Double(xc,yc); } // Drawing background if (conf._drawBackground) out.setBackgroundColor(conf._backgroundColor); // Drawing region highlights renderRegionHighlights(out,coords,centers); // Drawing backbone out.setColor(conf._backboneColor); for (int i = 1; i < _listeBases.size(); i++) { Point2D.Double p1 = coords[i-1]; Point2D.Double p2 = coords[i]; x0 = p1.x; y0 = p1.y; x1 = p2.x; y1 = p2.y; Point2D.Double vn = new Point2D.Double(); double dist = p1.distance(p2); boolean discontinuous= (getBaseNumber(i)-getBaseNumber(i-1)!=1); int a = _listeBases.get(i-1).getElementStructure(); int b = _listeBases.get(i).getElementStructure(); boolean consecutive = (a==i)&&(b==i-1); if (dist>0){ vn.x = (x1-x0)/dist; vn.y = (y1-y0)/dist; if (consecutive && (getDrawMode()!=RNA.DRAW_MODE_LINEAR) && (getDrawMode()!=RNA.DRAW_MODE_CIRCULAR)) { int dir = 0; if (i+1<coords.length) {dir = (testDirectionality(i-1,i,i+1)?-1:1);} else if (i-2>=0) {dir = (testDirectionality(i-2,i-1,i)?-1:1);} Point2D.Double centerSeg = new Point2D.Double((p1.x+p2.x)/2.0,(p1.y+p2.y)/2.0); double centerDist = RNA.VIRTUAL_LOOP_RADIUS*scale; Point2D.Double centerLoop = new Point2D.Double(centerSeg.x+centerDist*dir*vn.y,centerSeg.y-centerDist*dir*vn.x); out.drawLine(centerLoop.x-5, centerLoop.y,centerLoop.x+5, centerLoop.y, 2.0); out.drawLine(centerLoop.x, centerLoop.y-5,centerLoop.x, centerLoop.y+5, 2.0); double radius = centerLoop.distance(p1); double a1 = 360.*(Math.atan2(dir*(p1.x-centerLoop.x),dir*(p1.y-centerLoop.y)))/(2.*Math.PI); double a2 = 360.*(Math.atan2(dir*(centerLoop.x-p2.x),dir*(centerLoop.y-p2.y)))/(2.*Math.PI); if (a1<0){a1+=360.;} if (a2<0){a2+=360.;} double angle = a2-a1; if (dir*(a2-a1)<0.) angle += dir*360.; out.drawArc(centerLoop, 2.*radius, 2.*radius, a1, angle); } else { out.drawLine((x0+BASE_RADIUS*vn.x), (y0+BASE_RADIUS*vn.y), (x1-BASE_RADIUS*vn.x), (y1-BASE_RADIUS*vn.y),1.0); } } } // Drawing bonds for (int i = 0; i < _listeBases.size(); i++) { if (_listeBases.get(i).getElementStructure() > i) { ModeleBP style = _listeBases.get(i).getStyleBP(); if (style.isCanonical() || conf._drawnNonCanonicalBP) { Color bpcol = getBasePairColor(style, conf); out.setColor(bpcol); int j = _listeBases.get(i).getElementStructure(); x0 = coords[i].x; y0 = coords[i].y; x1 = coords[j].x; y1 = coords[j].y; dx = x1 - x0; dy = y1 - y0; norm = Math.sqrt(dx * dx + dy * dy); dx /= norm; dy /= norm; if (_drawMode == DRAW_MODE_CIRCULAR || _drawMode == DRAW_MODE_RADIATE || _drawMode == DRAW_MODE_NAVIEW) { drawBasePair(out, new Point2D.Double(x0, y0), new Point2D.Double(x1, y1), style, conf); } else if (_drawMode == DRAW_MODE_LINEAR) { double coef; double distance; if (j - i == 1) coef = _bpHeightIncrement * 2; else coef = _bpHeightIncrement * 1; distance = (int) Math.round(x1 - x0); out.drawArc(new Point2D.Double(x0, y0), distance, distance * coef, 0, 180); } } } } // Drawing additional bonds if (conf._drawnNonPlanarBP) { for (int i = 0; i < _structureAux.size(); i++) { ModeleBP bp = _structureAux.get(i); out.setColor(getBasePairColor(bp, conf)); int a = bp.getPartner5().getIndex(); int b = bp.getPartner3().getIndex(); if (bp.isCanonical() || conf._drawnNonCanonicalBP) { x0 = coords[a].x; y0 = coords[a].y; x1 = coords[b].x; y1 = coords[b].y; dx = x1 - x0; dy = y1 - y0; norm = Math.sqrt(dx * dx + dy * dy); dx /= norm; dy /= norm; if ((_drawMode == DRAW_MODE_CIRCULAR) || (_drawMode == DRAW_MODE_RADIATE) || _drawMode == DRAW_MODE_NAVIEW) { drawBasePair(out, new Point2D.Double(x0, y0), new Point2D.Double(x1, y1), bp, conf); } else if (_drawMode == DRAW_MODE_LINEAR) { double coef; double distance; if (b - a == 1) coef = _bpHeightIncrement * 2; else coef = _bpHeightIncrement * 1; distance = (int) Math.round(x1 - x0); out.drawArc(new Point2D.Double(x0, y0), distance, distance * coef, 0, 180); } } } } // Drawing Bases double baseFontSize = (1.5 * BASE_RADIUS); out.setFont(PSExport.FONT_HELVETICA_BOLD, baseFontSize); if (conf._comparisonMode) { for (int i = 0; i < _listeBases.size(); i++) { x0 = coords[i].x; y0 = coords[i].y; out.fillCircle(x0, y0, BASE_RADIUS, 1l, getBaseInnerColor(i, conf)); out.setColor(getBaseOuterColor(i, conf)); out.drawCircle(x0, y0, BASE_RADIUS, 1l); out.setColor(getBaseNameColor(i, conf)); out.drawText(x0, y0, ((ModeleBasesComparison) _listeBases .get(i)).getBases()); } } else { for (int i = 0; i < _listeBases.size(); i++) { ModeleBase mb = _listeBases.get(i); x0 = coords[i].x; y0 = coords[i].y; if (conf._fillBase) { out.fillCircle(x0, y0, BASE_RADIUS, 1l, getBaseInnerColor(i, conf)); } if (conf._drawOutlineBase) { out.setColor(getBaseOuterColor(i, conf)); out.drawCircle(x0, y0, BASE_RADIUS, 1l); } out.setColor(getBaseNameColor(i, conf)); out.drawText(x0, y0, _listeBases.get(i).getContent()); } } // Drawing base numbers double numFontSize = (double) (1.5 * BASE_RADIUS); out.setFont(PSExport.FONT_HELVETICA_BOLD, numFontSize); for (int i = 0; i < _listeBases.size(); i++) { int basenum = _listeBases.get(i).getBaseNumber(); if (basenum == -1) { basenum = i + 1; } if (this.isNumberDrawn(_listeBases.get(i),conf._numPeriod)) { out.setColor(_listeBases.get(i).getStyleBase().get_base_number_color()); x0 = coords[i].x; y0 = coords[i].y; x1 = centers[i].x; y1 = centers[i].y; dx = x1 - x0; dy = y1 - y0; norm = Math.sqrt(dx * dx + dy * dy); dx /= norm; dy /= norm; out.drawLine((x0 - 1.5 * BASE_RADIUS * dx), (y0 - 1.5 * BASE_RADIUS * dy), (x0 - 2.5 * BASE_RADIUS * dx), (y0 - 2.5 * BASE_RADIUS * dy), 1); out.drawText((x0 - (conf._distNumbers+1.0) * BASE_RADIUS * dx), (y0 - (conf._distNumbers+1.0) * BASE_RADIUS * dy), "" + (basenum)); } } renderAnnotations(out, minX, minY,conf); // Draw color map if (conf._drawColorMap) { drawColorMap(conf, out); } // Drawing Title Rectangle2D.Double currentBBox = out.getBoundingBox(); double titleFontSize = (2.0*conf._titleFont.getSize()); out.setColor(conf._titleColor); out.setFont(PSExport.FONT_HELVETICA,titleFontSize); double yTitle = currentBBox.y-titleFontSize/2.0; if (!getName().equals("")) { out.drawText((maxX-minX)/2.0,yTitle, getName()); } else if (!conf._title.equals("")) { out.drawText((maxX-minX)/2.0,yTitle, getName()); } OutputStreamWriter fout; try { fout = new OutputStreamWriter(new FileOutputStream(path),"UTF-8"); fout.write(out.export()); fout.close(); } catch (IOException e) { throw new ExceptionWritingForbidden(e.getMessage()); } } Point2D.Double buildCaptionPosition(ModeleBase mb, double heightEstimate, VARNAConfig conf) { double radius = 2.0; if (isNumberDrawn(mb,conf._numPeriod)) { radius += (conf._distNumbers+1.0); } Point2D.Double center = mb.getCenter(); Point2D.Double p = mb.getCoords(); double realDistance = BASE_RADIUS*radius + heightEstimate; return new Point2D.Double(center.getX() + (p.getX() - center.getX()) * ((p.distance(center) + realDistance) / p .distance(center)), center.getY() + (p.getY() - center.getY()) * ((p.distance(center) + realDistance) / p .distance(center)) ); } public double getBPHeightIncrement() { return this._bpHeightIncrement; } public void setBPHeightIncrement(double d) { _bpHeightIncrement = d; } public static double CHEM_PROB_ARROW_THICKNESS = 2.0; private void drawChemProbAnnotation(SecStrDrawingProducer out, ChemProbAnnotation cpa, Point2D.Double anchor, double minX, double minY) { out.setColor(cpa.getColor()); Point2D.Double v = cpa.getDirVector(); Point2D.Double vn = cpa.getNormalVector(); Point2D.Double base = new Point2D.Double((anchor.x+CHEM_PROB_DIST*v.x),(anchor.y+CHEM_PROB_DIST*v.y)); Point2D.Double edge = new Point2D.Double((base.x+CHEM_PROB_BASE_LENGTH*cpa.getIntensity()*v.x),(base.y+CHEM_PROB_BASE_LENGTH*cpa.getIntensity()*v.y)); double thickness = CHEM_PROB_ARROW_THICKNESS*cpa.getIntensity(); switch (cpa.getType()) { case ARROW_TYPE: { Point2D.Double arrowTip1 = new Point2D.Double((base.x+cpa.getIntensity()*(CHEM_PROB_ARROW_WIDTH*vn.x+CHEM_PROB_ARROW_HEIGHT*v.x)), (base.y+cpa.getIntensity()*(CHEM_PROB_ARROW_WIDTH*vn.y+CHEM_PROB_ARROW_HEIGHT*v.y))); Point2D.Double arrowTip2 = new Point2D.Double((base.x+cpa.getIntensity()*(-CHEM_PROB_ARROW_WIDTH*vn.x+CHEM_PROB_ARROW_HEIGHT*v.x)), (base.y+cpa.getIntensity()*(-CHEM_PROB_ARROW_WIDTH*vn.y+CHEM_PROB_ARROW_HEIGHT*v.y))); out.drawLine(base.x-minX,minY-base.y,edge.x-minX,minY-edge.y,thickness); out.drawLine(base.x-minX,minY-base.y,arrowTip1.x-minX,minY-arrowTip1.y,thickness); out.drawLine(base.x-minX,minY-base.y,arrowTip2.x-minX,minY-arrowTip2.y,thickness); } break; case PIN_TYPE: { Point2D.Double side1 = new Point2D.Double((edge.x-cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*v.x)), (edge.y-cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*v.y))); Point2D.Double side2 = new Point2D.Double((edge.x-cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*vn.x)), (edge.y-cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*vn.y))); Point2D.Double side3 = new Point2D.Double((edge.x+cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*v.x)), (edge.y+cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*v.y))); Point2D.Double side4 = new Point2D.Double((edge.x+cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*vn.x)), (edge.y+cpa.getIntensity()*(CHEM_PROB_PIN_SEMIDIAG*vn.y))); GeneralPath p2 = new GeneralPath(); p2.moveTo((float)(side1.x-minX),(float)(minY-side1.y)); p2.lineTo((float)(side2.x-minX),(float)(minY-side2.y)); p2.lineTo((float)(side3.x-minX),(float)(minY-side3.y)); p2.lineTo((float)(side4.x-minX),(float)(minY-side4.y)); p2.closePath(); out.fillPolygon(p2, cpa.getColor()); out.drawLine(base.x-minX,minY-base.y,edge.x-minX,minY-edge.y,thickness); } break; case TRIANGLE_TYPE: { Point2D.Double arrowTip1 = new Point2D.Double((edge.x+cpa.getIntensity()*(CHEM_PROB_TRIANGLE_WIDTH*vn.x)), (edge.y+cpa.getIntensity()*(CHEM_PROB_TRIANGLE_WIDTH*vn.y))); Point2D.Double arrowTip2 = new Point2D.Double((edge.x+cpa.getIntensity()*(-CHEM_PROB_TRIANGLE_WIDTH*vn.x)), (edge.y+cpa.getIntensity()*(-CHEM_PROB_TRIANGLE_WIDTH*vn.y))); GeneralPath p2 = new GeneralPath(); p2.moveTo((float)(base.x-minX),(float)(minY-base.y)); p2.lineTo((float)(arrowTip1.x-minX),(float)(minY-arrowTip1.y)); p2.lineTo((float)(arrowTip2.x-minX),(float)(minY-arrowTip2.y)); p2.closePath(); out.fillPolygon(p2, cpa.getColor()); } break; case DOT_TYPE: { Double radius = CHEM_PROB_DOT_RADIUS*cpa.getIntensity(); Point2D.Double center = new Point2D.Double((base.x+radius*v.x)-minX,minY-(base.y+radius*v.y)); out.fillCircle(center.x, center.y, radius, thickness, cpa.getColor()); } break; } } private void renderAnnotations(SecStrDrawingProducer out, double minX, double minY, VARNAConfig conf) { for (TextAnnotation textAnnotation : getAnnotations()) { out.setColor(textAnnotation.getColor()); out.setFont(PSExport.FONT_HELVETICA_BOLD,2.0*textAnnotation.getFont().getSize()); Point2D.Double position = textAnnotation.getCenterPosition(); if (textAnnotation.getType()==TextAnnotation.BASE) { ModeleBase mb = (ModeleBase) textAnnotation.getAncrage(); double fontHeight = Math.ceil(textAnnotation.getFont().getSize()); position = buildCaptionPosition(mb,fontHeight,conf); } out.drawText(position.x-minX,-(position.y-minY), textAnnotation.getTexte()); } for (ChemProbAnnotation cpa : getChemProbAnnotations()) { Point2D.Double anchor = cpa.getAnchorPosition(); drawChemProbAnnotation(out,cpa,anchor,minX,minY); } } public boolean isNumberDrawn(ModeleBase mb, int numPeriod) { if (numPeriod <= 0 ) return false; return ((mb.getIndex() == 0) || ((mb.getBaseNumber()) % numPeriod == 0) || (mb.getIndex() == get_listeBases().size() - 1)); } public void saveRNAEPS(String path, VARNAConfig conf) throws ExceptionWritingForbidden { PSExport out = new PSExport(); saveRNA(path, conf, 0.4, out); } public void saveRNAXFIG(String path, VARNAConfig conf) throws ExceptionWritingForbidden { XFIGExport out = new XFIGExport(); saveRNA(path, conf, 20, out); } public void saveRNASVG(String path, VARNAConfig conf) throws ExceptionWritingForbidden { SVGExport out = new SVGExport(); saveRNA(path, conf, 0.5, out); } public Rectangle2D.Double getBBox() { Rectangle2D.Double result = new Rectangle2D.Double(10, 10, 10, 10); double minx, maxx, miny, maxy; minx = Double.MAX_VALUE; miny = Double.MAX_VALUE; maxx = -Double.MAX_VALUE; maxy = -Double.MAX_VALUE; for (int i = 0; i < _listeBases.size(); i++) { minx = Math.min(_listeBases.get(i).getCoords().getX() - BASE_RADIUS, minx); miny = Math.min(_listeBases.get(i).getCoords().getY() - BASE_RADIUS, miny); maxx = Math.max(_listeBases.get(i).getCoords().getX() + BASE_RADIUS, maxx); maxy = Math.max(_listeBases.get(i).getCoords().getY() + BASE_RADIUS, maxy); } result.x = minx; result.y = miny; result.width = Math.max(maxx - minx,1); result.height = Math.max(maxy - miny,1); if (_drawMode==RNA.DRAW_MODE_LINEAR) { double realHeight = _bpHeightIncrement*result.width/2.0; result.height += realHeight; result.y -= realHeight; } return result; } public void setCoord(int index, Point2D.Double p) { setCoord(index, p.x, p.y); } public void setCoord(int index, double x, double y) { if (index < _listeBases.size()) { _listeBases.get(index).setCoords(new Point2D.Double(x, y)); } } public Point2D.Double getCoords(int i) { if (i < _listeBases.size() && i >= 0) { return _listeBases.get(i).getCoords(); } return new Point2D.Double(); } public String getBaseContent(int i) { if ((i>=0)&&(i < _listeBases.size())) { return _listeBases.get(i).getContent(); } return ""; } public int getBaseNumber(int i) { if ((i>=0)&&(i < _listeBases.size())) { return _listeBases.get(i).getBaseNumber(); } return -1; } public Point2D.Double getCenter(int i) { if (i < _listeBases.size()) { return _listeBases.get(i).getCenter(); } return new Point2D.Double(); } public void setCenter(int i, double x, double y) { setCenter(i, new Point2D.Double(x,y)); } public void setCenter(int i, Point2D.Double p) { if (i < _listeBases.size()) { _listeBases.get(i).setCenter(p); } } public void drawRNACircle() { _drawn = true; _drawMode = DRAW_MODE_CIRCULAR; int radius = (int) ((3 * (_listeBases.size() + 1) * BASE_RADIUS) / (2 * Math.PI)); double angle; for (int i = 0; i < _listeBases.size(); i++) { angle = -((((double) -(i + 1)) * 2.0 * Math.PI) / ((double) (_listeBases.size() + 1)) - Math.PI / 2.0); _listeBases.get(i).setCoords( new Point2D.Double( (radius * Math.cos(angle) * _spaceBetweenBases), (radius * Math.sin(angle) * _spaceBetweenBases))); _listeBases.get(i).setCenter(new Point2D.Double(0, 0)); } } public void drawRNAVARNAView() { _drawn = true; _drawMode = DRAW_MODE_VARNA_VIEW; VARNASecDraw vs = new VARNASecDraw(); vs.drawRNA(1, this); } public void drawRNALine() { _drawn = true; _drawMode = DRAW_MODE_LINEAR; for (int i = 0; i < get_listeBases().size(); i++) { get_listeBases().get(i).setCoords( new Point2D.Double(i * _spaceBetweenBases * 20, 0)); get_listeBases().get(i).setCenter( new Point2D.Double(i * _spaceBetweenBases * 20, -10)); } } /** * IN: Argument helixEndPoint is an IN argument (will be read), * and must contain an helix edge endpoint. * * The other arguments are OUT arguments * (must be existing objects, content will be overwritten). * * OUT: The i argument will contain a vector colinear to the vector * from the helix startPosition to endPosition or the opposite * depending on there the endpoint is (the endpoint will be on the * destination side of the vector). ||i|| = 1 * * OUT: The j vector will contain an vector that is colinear * to the last/first base pair connection on the side of this endpoint. * The vector will be oriented to the side of the given endpoint. * ||j|| = 1 */ private void computeHelixEndPointDirections( EdgeEndPoint helixEndPoint, // IN Point2D.Double i, // OUT Point2D.Double j // OUT ) { RNATemplateHelix helix = (RNATemplateHelix) helixEndPoint.getElement(); Point2D.Double startpos = helix.getStartPosition(); Point2D.Double endpos = helix.getEndPosition(); Point2D.Double helixVector = new Point2D.Double(); switch (helixEndPoint.getPosition()) { case IN1: case OUT2: helixVector.x = startpos.x - endpos.x; helixVector.y = startpos.y - endpos.y; break; case IN2: case OUT1: helixVector.x = endpos.x - startpos.x; helixVector.y = endpos.y - startpos.y; break; } double helixVectorLength = Math.hypot(helixVector.x, helixVector.y); // i is the vector which is colinear to helixVector and such that ||i|| = 1 i.x = helixVector.x / helixVectorLength; i.y = helixVector.y / helixVectorLength; // Find j such that it is orthogonal to i, ||j|| = 1 // and j goes to the side where the sequence will be connected switch (helixEndPoint.getPosition()) { case IN1: case IN2: // rotation of +pi/2 j.x = - i.y; j.y = i.x; break; case OUT1: case OUT2: // rotation of -pi/2 j.x = i.y; j.y = - i.x; break; } if (helix.isFlipped()) { j.x = - j.x; j.y = - j.y; } } /** * A cubic Bezier curve can be defined by 4 points, * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves * For each of the curve end points, there is the last/first point of the * curve and a point which gives the direction and length of the tangent * vector on that side. This two points are respectively curveEndPoint * and curveVectorOtherPoint. * IN: Argument helixVector is the vector formed by the helix, * in the right direction for our sequence. * IN: Argument curveEndPoint is the position of the endpoint on the helix. * OUT: Argument curveVectorOtherPoint must be allocated * and the values will be modified. */ private void computeBezierTangentVectorTarget( EdgeEndPoint endPoint, Point2D.Double curveEndPoint, Point2D.Double curveVectorOtherPoint) throws RNATemplateDrawingAlgorithmException { boolean sequenceEndPointIsIn; RNATemplateUnpairedSequence sequence; if (endPoint.getElement() instanceof RNATemplateHelix) { sequence = (RNATemplateUnpairedSequence) endPoint.getOtherElement(); EdgeEndPointPosition endPointPositionOnHelix = endPoint.getPosition(); switch (endPointPositionOnHelix) { case IN1: case IN2: sequenceEndPointIsIn = false; break; default: sequenceEndPointIsIn = true; } EdgeEndPoint endPointOnHelix = sequenceEndPointIsIn ? sequence.getIn().getOtherEndPoint() : sequence.getOut().getOtherEndPoint(); if (endPointOnHelix == null) { throw (new RNATemplateDrawingAlgorithmException("Sequence is not connected to an helix.")); } } else { // The endpoint is on an unpaired sequence. sequence = (RNATemplateUnpairedSequence) endPoint.getElement(); if (endPoint == sequence.getIn()) { // endpoint is 5' sequenceEndPointIsIn = true; } else { sequenceEndPointIsIn = false; } } double l = sequenceEndPointIsIn ? sequence.getInTangentVectorLength() : sequence.getOutTangentVectorLength(); // Compute the absolute angle our line makes to the helix double theta = sequenceEndPointIsIn ? sequence.getInTangentVectorAngle() : sequence.getOutTangentVectorAngle(); // Compute v, the tangent vector of the Bezier curve Point2D.Double v = new Point2D.Double(); v.x = l * Math.cos(theta); v.y = l * Math.sin(theta); curveVectorOtherPoint.x = curveEndPoint.x + v.x; curveVectorOtherPoint.y = curveEndPoint.y + v.y; } /** * Compute the angle made by a vector. */ private static double angleFromVector(Point2D.Double v) { return angleFromVector(v.x, v.y); } private static double angleFromVector(double x, double y) { double l = Math.hypot(x, y); if (y > 0) { return Math.acos(x / l); } else if (y < 0) { return - Math.acos(x / l); } else { return x > 0 ? 0 : Math.PI; } } /** * Compute (actual helix length / helix length in template). */ private double computeLengthIncreaseFactor( int[] basesInHelixArray, // IN RNATemplateHelix helix // IN ) { double templateLength = computeHelixTemplateLength(helix); double realLength = computeHelixRealLength(basesInHelixArray); return realLength / templateLength; } /** * Compute (actual helix vector - helix vector in template). */ private Point2D.Double computeLengthIncreaseDelta( int[] basesInHelixArray, // IN RNATemplateHelix helix // IN ) { double templateLength = computeHelixTemplateLength(helix); double realLength = computeHelixRealLength(basesInHelixArray); Point2D.Double i = new Point2D.Double(); computeTemplateHelixVectors(helix, null, i, null); return new Point2D.Double(i.x*(realLength-templateLength), i.y*(realLength-templateLength)); } /** * Compute helix interesting vectors from template helix. * @param helix The template helix you want to compute the vectors from. * @param o This point coordinates will be set the origin of the helix (or not if null), * ie. the point in the middle of the base pair with the two most extreme bases. * @param i Will be set to the normalized helix vector. (nothing done if null) * @param j Will be set to the normalized helix base pair vector (5' -> 3'). (nothing done if null) */ private void computeTemplateHelixVectors( RNATemplateHelix helix, // IN Point2D.Double o, // OUT Point2D.Double i, // OUT Point2D.Double j // OUT ) { Point2D.Double startpos, endpos; if (helix.getIn1Is() == In1Is.IN1_IS_5PRIME) { startpos = helix.getStartPosition(); endpos = helix.getEndPosition(); } else { endpos = helix.getStartPosition(); startpos = helix.getEndPosition(); } if (o != null) { o.x = startpos.x; o.y = startpos.y; } if (i != null || j != null) { // (i_x,i_y) is the vector between two consecutive bases of the same side of an helix if (i == null) i = new Point2D.Double(); i.x = (endpos.x - startpos.x); i.y = (endpos.y - startpos.y); double i_original_norm = Math.hypot(i.x, i.y); // change its norm to 1 i.x = i.x / i_original_norm; i.y = i.y / i_original_norm; if (j != null) { j.x = - i.y; j.y = i.x; if (helix.isFlipped()) { j.x = - j.x; j.y = - j.y; } double j_original_norm = Math.hypot(j.x, j.y); // change (j_x,j_y) so that its norm is 1 j.x = j.x / j_original_norm; j.y = j.y / j_original_norm; } } } private static class ComputeArcCenter { /** * Given an arc length (l) and segment length (delta) of the arc, * find where to put the center, returned as a position of the perpendicular * bisector of the segment. The positive side is the one where the arc is drawn. * It works using Newton's method. */ public static double computeArcCenter(double delta, double l) { double x_n = 0; double x_n_plus_1, f_x_n, f_x_n_plus_1; int steps = 0; while (true) { f_x_n = f(x_n,delta); x_n_plus_1 = x_n - (f_x_n - l)/fprime(x_n,delta); f_x_n_plus_1 = f(x_n_plus_1,delta); steps++; // We want a precision of 0.1 on arc length if (x_n_plus_1 == Double.NEGATIVE_INFINITY || Math.abs(f_x_n_plus_1 - f_x_n) < 0.1) { //System.out.println("computeArcCenter: steps = " + steps + " result = " + x_n_plus_1); return x_n_plus_1; } x_n = x_n_plus_1; f_x_n = f_x_n_plus_1; } } public static double f(double c, double delta) { if (c < 0) { return 2*Math.atan(delta/(-2*c)) * Math.sqrt(delta*delta/4 + c*c); } else if (c != 0) { // c > 0 return (2*Math.PI - 2*Math.atan(delta/(2*c))) * Math.sqrt(delta*delta/4 + c*c); } else { // c == 0 return Math.PI * Math.sqrt(delta*delta/4 + c*c); } } /** * d/dc f(c,delta) */ public static double fprime(double c, double delta) { if (c < 0) { return delta/(c*c + delta/4)*Math.sqrt(delta*delta/4 + c*c) + 2*Math.atan(delta/(-2*c))*c/Math.sqrt(delta*delta/4 + c*c); } else if (c != 0) { // c > 0 return delta/(c*c + delta/4)*Math.sqrt(delta*delta/4 + c*c) + (2*Math.PI - 2*Math.atan(delta/(-2*c)))*c/Math.sqrt(delta*delta/4 + c*c); } else { // c == 0 return 2; } } } /** * Estimate bulge arc length. */ private double estimateBulgeArcLength(int firstBase, int lastBase) { if (firstBase + 1 == lastBase) return LOOP_DISTANCE; // there is actually no bulge double len = 0.0; int k = firstBase; while (k < lastBase) { int l = _listeBases.get(k).getElementStructure(); if (k < l && l < lastBase) { len += BASE_PAIR_DISTANCE; k = l; } else { len += LOOP_DISTANCE; k++; } } return len; } /** * Estimate bulge width, the given first and last bases must be those in the helix. */ private double estimateBulgeWidth(int firstBase, int lastBase) { double len = estimateBulgeArcLength(firstBase, lastBase); return 2 * (len / Math.PI); } /** * Estimate bulge width, the given first and last bases must be those in the helix. */ private double estimateBulgeWidthOLD(int firstBase, int lastBase) { if (firstBase + 1 == lastBase) return LOOP_DISTANCE; // there is actually no bulge double len = estimateBulgeArcLength(firstBase, lastBase); // We expect the bases to be drawn on a circle having its center in the middle of the helix. len += BASE_PAIR_DISTANCE; // 2 * half base pair part of the helix return 2 * (len / Math.PI); } /** * Get helix length in template. */ private double computeHelixTemplateLength(RNATemplateHelix helix) { return Math.hypot(helix.getStartPosition().x - helix.getEndPosition().x, helix.getStartPosition().y - helix.getEndPosition().y); } /** * Compute helix actual length (as drawHelixLikeTemplateHelix() would draw it). */ private double computeHelixRealLength(int[] basesInHelixArray) { return drawHelixLikeTemplateHelix(basesInHelixArray, null, null, null, 0, null); } /** * Draw the given helix (given as a *SORTED* array of indexes) * like defined in the given template helix. * OUT: The bases positions are not changed in fact, * instead the coords and centers arrays are modified. * IN: The helix origin position is multiplied by scaleHelixOrigin * and translateVectors.get(helix) is added. * RETURN VALUE: * The length of the drawn helix. * */ private double drawHelixLikeTemplateHelix( int[] basesInHelixArray, // IN RNATemplateHelix helix, // IN (optional, ie. may be null) Point2D.Double[] coords, // OUT (optional, ie. may be null) Point2D.Double[] centers, // OUT (optional, ie. may be null) double scaleHelixOrigin, // IN Map<RNATemplateHelix, Point2D.Double> translateVectors // IN (optional, ie. may be null) ) { int n = basesInHelixArray.length / 2; if (n == 0) return 0; // Default values when not template helix is provided: Point2D.Double o = new Point2D.Double(0, 0); Point2D.Double i = new Point2D.Double(1, 0); Point2D.Double j = new Point2D.Double(0, 1); boolean flipped = false; if (helix != null) { computeTemplateHelixVectors(helix, o, i, j); flipped = helix.isFlipped(); } Point2D.Double li = new Point2D.Double(i.x*LOOP_DISTANCE, i.y*LOOP_DISTANCE); // We want o to be the point where the first base (5' end) is o.x = (o.x - j.x * BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin; o.y = (o.y - j.y * BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin; if (translateVectors != null && translateVectors.containsKey(helix)) { Point2D.Double v = translateVectors.get(helix); o.x = o.x + v.x; o.y = o.y + v.y; } // We need this array so that we can store positions even if coords == null Point2D.Double[] helixBasesPositions = new Point2D.Double[basesInHelixArray.length]; for (int k=0; k<helixBasesPositions.length; k++) { helixBasesPositions[k] = new Point2D.Double(); } Point2D.Double accDelta = new Point2D.Double(0, 0); for (int k=0; k<n; k++) { int kp = 2*n-k-1; Point2D.Double p1 = helixBasesPositions[k]; // we assign the point *reference* Point2D.Double p2 = helixBasesPositions[kp]; // Do we have a bulge between previous base pair and this one? boolean bulge = k >= 1 && (basesInHelixArray[k] != basesInHelixArray[k-1] + 1 || basesInHelixArray[kp+1] != basesInHelixArray[kp] + 1); if (k >= 1) { if (basesInHelixArray[k] < basesInHelixArray[k-1] || basesInHelixArray[kp+1] < basesInHelixArray[kp]) { throw new Error("Internal bug: basesInHelixArray must be sorted"); } if (bulge) { // Estimate a good distance (delta) between the previous base pair and this one double delta1 = estimateBulgeWidth(basesInHelixArray[k-1], basesInHelixArray[k]); double delta2 = estimateBulgeWidth(basesInHelixArray[kp], basesInHelixArray[kp+1]); // The biggest bulge defines the width double delta = Math.max(delta1, delta2); if (coords != null) { // Now, where do we put the bases that are part of the bulge? for (int side=0; side<2; side++) { Point2D.Double pstart = new Point2D.Double(); Point2D.Double pend = new Point2D.Double(); Point2D.Double bisectVect = new Point2D.Double(); Point2D.Double is = new Point2D.Double(); int firstBase, lastBase; double alphasign = flipped ? -1 : 1; if (side == 0) { firstBase = basesInHelixArray[k-1]; lastBase = basesInHelixArray[k]; pstart.setLocation(o.x + accDelta.x, o.y + accDelta.y); pend.setLocation(o.x + accDelta.x + i.x*delta, o.y + accDelta.y + i.y*delta); bisectVect.setLocation(-j.x, -j.y); is.setLocation(i); } else { firstBase = basesInHelixArray[kp]; lastBase = basesInHelixArray[kp+1]; pstart.setLocation(o.x + accDelta.x + i.x*delta + j.x*BASE_PAIR_DISTANCE, o.y + accDelta.y + i.y*delta + j.y*BASE_PAIR_DISTANCE); pend.setLocation(o.x + accDelta.x + j.x*BASE_PAIR_DISTANCE, o.y + accDelta.y + j.y*BASE_PAIR_DISTANCE); bisectVect.setLocation(j); is.setLocation(-i.x, -i.y); } double arclen = estimateBulgeArcLength(firstBase, lastBase); double centerOnBisect = ComputeArcCenter.computeArcCenter(delta, arclen); // Should we draw the base on an arc or simply use a line? if (centerOnBisect > -1000) { Point2D.Double center = new Point2D.Double(pstart.x + is.x*delta/2 + bisectVect.x*centerOnBisect, pstart.y + is.y*delta/2 + bisectVect.y*centerOnBisect); int b = firstBase; double len = 0; double r = Math.hypot(pstart.x - center.x, pstart.y - center.y); double alpha0 = angleFromVector(pstart.x - center.x, pstart.y - center.y); while (b < lastBase) { int l = _listeBases.get(b).getElementStructure(); if (b < l && l < lastBase) { len += BASE_PAIR_DISTANCE; b = l; } else { len += LOOP_DISTANCE; b++; } if (b < lastBase) { coords[b].x = center.x + r*Math.cos(alpha0 + alphasign*len/r); coords[b].y = center.y + r*Math.sin(alpha0 + alphasign*len/r); } } } else { // Draw on a line double nBP = 0; double nLD = 0; { int b = firstBase; while (b < lastBase) { int l = _listeBases.get(b).getElementStructure(); if (b < l && l < lastBase) { nBP++; b = l; } else { nLD++; b++; } } } // Distance between paired bases cannot be changed // (imposed by helix width) but distance between other // bases can be adjusted. double LD = Math.max((delta - nBP*BASE_PAIR_DISTANCE) / nLD, 0); //System.out.println("nBP=" + nBP + " nLD=" + nLD); double len = 0; { int b = firstBase; while (b < lastBase) { int l = _listeBases.get(b).getElementStructure(); if (b < l && l < lastBase) { len += BASE_PAIR_DISTANCE; b = l; } else { len += LD; b++; } if (b < lastBase) { coords[b].x = pstart.x + is.x*len; coords[b].y = pstart.y + is.y*len; } } } //System.out.println("len=" + len + " delta=" + delta + " d(pstart,pend)=" + Math.hypot(pend.x-pstart.x, pend.y-pstart.y)); } // Does the bulge contain an helix? // If so, use drawLoop() to draw it. { int b = firstBase; while (b < lastBase) { int l = _listeBases.get(b).getElementStructure(); if (b < l && l < lastBase) { // Helix present in bulge Point2D.Double b1pos = coords[b]; Point2D.Double b2pos = coords[l]; double beta = angleFromVector(b2pos.x - b1pos.x, b2pos.y - b1pos.y) - Math.PI / 2 + (flipped ? Math.PI : 0); Point2D.Double loopCenter = new Point2D.Double((b1pos.x + b2pos.x)/2, (b1pos.y + b2pos.y)/2); drawLoop(b, l, loopCenter.x, loopCenter.y, beta, coords, centers); // If the helix is flipped, we need to compute the symmetric // of the whole loop. if (helix.isFlipped()) { Point2D.Double v = new Point2D.Double(Math.cos(beta), Math.sin(beta)); Point2D.Double[] points1 = new Point2D.Double[l-b+1]; Point2D.Double[] points2 = new Point2D.Double[l-b+1]; for (int c=b; c<=l; c++) { // This is an assignment by reference. points1[c-b] = coords[c]; points2[c-b] = centers[c]; } symmetric(loopCenter, v, points1); symmetric(loopCenter, v, points2); } // Continue b = l; } else { b++; } } } } } accDelta.x += i.x*delta; accDelta.y += i.y*delta; p1.x = o.x + accDelta.x; p1.y = o.y + accDelta.y; p2.x = p1.x + j.x*BASE_PAIR_DISTANCE; p2.y = p1.y + j.y*BASE_PAIR_DISTANCE; } else { accDelta.x += li.x; accDelta.y += li.y; p1.x = o.x + accDelta.x; p1.y = o.y + accDelta.y; p2.x = p1.x + j.x*BASE_PAIR_DISTANCE; p2.y = p1.y + j.y*BASE_PAIR_DISTANCE; } } else { // First base pair p1.x = o.x; p1.y = o.y; p2.x = p1.x + j.x*BASE_PAIR_DISTANCE; p2.y = p1.y + j.y*BASE_PAIR_DISTANCE; } } Point2D.Double p1 = helixBasesPositions[0]; Point2D.Double p2 = helixBasesPositions[n-1]; if (coords != null) { for (int k=0; k<helixBasesPositions.length; k++) { coords[basesInHelixArray[k]] = helixBasesPositions[k]; } } return Math.hypot(p2.x-p1.x, p2.y-p1.y); } private void drawHelixLikeTemplateHelixOLD( int[] basesInHelixArray, // IN RNATemplateHelix helix, // IN Point2D.Double[] coords, // OUT Point2D.Double[] centers, // OUT double scaleHelixOrigin, // IN Map<RNATemplateHelix, Point2D.Double> translateVectors // IN ) { int n = basesInHelixArray.length / 2; Point2D.Double o = new Point2D.Double(); Point2D.Double i = new Point2D.Double(); Point2D.Double j = new Point2D.Double(); computeTemplateHelixVectors(helix, o, i, j); i.x = i.x * LOOP_DISTANCE; i.y = i.y * LOOP_DISTANCE; o.x = (o.x - j.x * BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin; o.y = (o.y - j.y * BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin; if (translateVectors != null && translateVectors.containsKey(helix)) { Point2D.Double v = translateVectors.get(helix); o.x = o.x + v.x; o.y = o.y + v.y; } for (int k=0; k<n; k++) { int b1 = basesInHelixArray[k]; int b2 = basesInHelixArray[2*n-k-1]; Point2D.Double p1 = coords[b1]; // we assign the point *reference* p1.x = o.x + k*i.x; p1.y = o.y + k*i.y; Point2D.Double p2 = coords[b2]; p2.x = p1.x + j.x * BASE_PAIR_DISTANCE; p2.y = p1.y + j.y * BASE_PAIR_DISTANCE; } for (int k=0; k<2*n-1; k++) { if (k == n-1) continue; int b1 = basesInHelixArray[k]; int b2 = basesInHelixArray[k+1]; if (b1 + 1 != b2) { // There is a loop between these 2 bases Point2D.Double b1pos = coords[b1]; Point2D.Double b2pos = coords[b2]; // ||v|| = 1 Point2D.Double v = new Point2D.Double(); if (k >= n) { v.x = j.x; v.y = j.y; } else { v.x = - j.x; v.y = - j.y; } Point2D.Double loopCenter = new Point2D.Double(); loopCenter.x = (b1pos.x + b2pos.x)/2 + v.x * LOOP_DISTANCE; loopCenter.y = (b1pos.y + b2pos.y)/2 + v.y * LOOP_DISTANCE; drawLoop(b1+1, b2-1, loopCenter.x, loopCenter.y, angleFromVector(v), coords, centers); // If the helix is flipped, we need to compute the symmetric // of the whole loop. if (helix.isFlipped()) { int from = b1+1; int to = b2-1; Point2D.Double[] points1 = new Point2D.Double[to-from+1]; Point2D.Double[] points2 = new Point2D.Double[to-from+1]; for (int b=from; b<=to; b++) { // This is an assignment by reference. points1[b-from] = coords[b]; points2[b-from] = centers[b]; } symmetric(loopCenter, v, points1); symmetric(loopCenter, v, points2); } } } } /** * A Bezier curve can be defined by four points, * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves * Here we give this four points and an array of bases indexes * (which must be indexes in this RNA sequence) which will be moved * to be on the Bezier curve. * The bases positions are not changed in fact, instead the coords and * centers arrays are modified. */ private void drawOnBezierCurve(int[] basesInSequence, Point2D.Double P0, Point2D.Double P1, Point2D.Double P2, Point2D.Double P3, Point2D.Double[] coords, Point2D.Double[] centers) { // Draw the bases of the sequence along a Bezier curve int n = basesInSequence.length; // We choose to approximate the Bezier curve by 10*n straight lines. CubicBezierCurve bezier = new CubicBezierCurve(P0, P1, P2, P3, 10*n); double curveLength = bezier.getApproxCurveLength(); double delta_t = curveLength / (n+1); double[] t = new double[n]; for (int k=0; k<n; k++) { t[k] = (k+1) * delta_t; } Point2D.Double[] sequenceBasesCoords = bezier.uniformParam(t); for (int k=0; k<n; k++) { coords[basesInSequence[k]] = sequenceBasesCoords[k]; } } /** * Like drawOnBezierCurve(), but on a straight line. */ private void drawOnStraightLine(int[] basesInSequence, Point2D.Double P0, Point2D.Double P3, Point2D.Double[] coords, Point2D.Double[] centers) { // Draw the bases of the sequence along a Bezier curve int n = basesInSequence.length; Point2D.Double v = new Point2D.Double(P3.x - P0.x, P3.y - P0.y); for (int k=0; k<n; k++) { coords[basesInSequence[k]].x = P0.x + (k+1) / (double) (n+1) * v.x; coords[basesInSequence[k]].y = P0.y + (k+1) / (double) (n+1) * v.y; } } /** * This functions draws the RNA sequence between (including) * firstBase and lastBase along a curve. * The sequence may contain helices. * * Bezier curve: * A Bezier curve can be defined by four points, * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves * * Straight line: * If P1 and P2 are null, the bases are drawn on a straight line. * * OUT: The bases positions are not changed in fact, * instead the coords and centers arrays are modified. * */ private void drawAlongCurve( int firstBase, int lastBase, Point2D.Double P0, Point2D.Double P1, Point2D.Double P2, Point2D.Double P3, Point2D.Double[] coords, Point2D.Double[] centers) { // First we find the bases which are directly on the Bezier curve ArrayList<Integer> alongBezierCurve = new ArrayList<Integer>(); for (int depth=0, i=firstBase; i<=lastBase; i++) { int k = _listeBases.get(i).getElementStructure(); if (k < 0 || k > lastBase || k < firstBase) { if (depth == 0) { alongBezierCurve.add(i); } } else { if (i < k) { if (depth == 0) { alongBezierCurve.add(i); alongBezierCurve.add(k); } depth++; } else { depth--; } } } // number of bases along the Bezier curve int n = alongBezierCurve.size(); int[] alongBezierCurveArray = RNATemplateAlign.intArrayFromList(alongBezierCurve); if (n > 0) { if (P1 != null && P2 != null) { drawOnBezierCurve(alongBezierCurveArray, P0, P1, P2, P3, coords, centers); } else { drawOnStraightLine(alongBezierCurveArray, P0, P3, coords, centers); } } // Now use the radiate algorithm to draw the helixes for (int k=0; k<n-1; k++) { int b1 = alongBezierCurveArray[k]; int b2 = alongBezierCurveArray[k+1]; if (_listeBases.get(b1).getElementStructure() == b2) { Point2D.Double b1pos = coords[b1]; Point2D.Double b2pos = coords[b2]; double alpha = angleFromVector(b2pos.x - b1pos.x, b2pos.y - b1pos.y); drawLoop(b1, b2, (b1pos.x + b2pos.x)/2, (b1pos.y + b2pos.y)/2, alpha - Math.PI / 2, coords, centers); } } } /** * Compute the symmetric of all the points in the points array * relative to the line that goes through p and has director vector v. * The array is modified in-place. */ private static void symmetric( Point2D.Double p, Point2D.Double v, Point2D.Double[] points) { // ||v||^2 double lv = v.x*v.x + v.y*v.y; for (int i=0; i<points.length; i++) { // A is the coordinates of points[i] after moving the origin at p Point2D.Double A = new Point2D.Double(points[i].x - p.x, points[i].y - p.y); // Symmetric of A Point2D.Double B = new Point2D.Double( -(A.x*v.y*v.y - 2*A.y*v.x*v.y - A.x*v.x*v.x) / lv, (A.y*v.y*v.y + 2*A.x*v.x*v.y - A.y*v.x*v.x) / lv); // Change the origin back points[i].x = B.x + p.x; points[i].y = B.y + p.y; } } private void computeHelixTranslations( Tree<RNANodeValueTemplate> tree, // IN Map<RNATemplateHelix, Point2D.Double> translateVectors, // OUT (must be initialized) RNATemplateMapping mapping, // IN Point2D.Double parentDeltaVector // IN ) { RNANodeValueTemplate nvt = tree.getValue(); Point2D.Double newDeltaVector = parentDeltaVector; if (nvt instanceof RNANodeValueTemplateBasePair) { RNATemplateHelix helix = ((RNANodeValueTemplateBasePair) nvt).getHelix(); if (! translateVectors.containsKey(helix)) { translateVectors.put(helix, parentDeltaVector); int[] basesInHelixArray; if (mapping.getAncestor(helix) != null) { basesInHelixArray = RNATemplateAlign.intArrayFromList(mapping.getAncestor(helix)); Arrays.sort(basesInHelixArray); } else { basesInHelixArray = new int[0]; } Point2D.Double helixDeltaVector = computeLengthIncreaseDelta(basesInHelixArray, helix); newDeltaVector = new Point2D.Double(parentDeltaVector.x+helixDeltaVector.x, parentDeltaVector.y+helixDeltaVector.y); } } for (Tree<RNANodeValueTemplate> subtree: tree.getChildren()) { computeHelixTranslations(subtree, translateVectors, mapping, newDeltaVector); } } private Map<RNATemplateHelix, Point2D.Double> computeHelixTranslations( Tree<RNANodeValueTemplate> tree, // IN RNATemplateMapping mapping // IN ) { Map<RNATemplateHelix, Point2D.Double> translateVectors = new HashMap<RNATemplateHelix, Point2D.Double>(); computeHelixTranslations(tree, translateVectors, mapping, new Point2D.Double(0,0)); return translateVectors; } /** * Same as below, with default helixLengthAdjustmentMethod. */ public RNATemplateMapping drawRNATemplate(RNATemplate template) throws RNATemplateDrawingAlgorithmException { return drawRNATemplate(template, DrawRNATemplateMethod.NOADJUST); } /** * What to do in case some helices are of a different length * in the template and the actual helix. * Possibles values are: * 0 - No adjustment is done. * A longer than expected helix might bump into an other helix. * 1 - Scaling factors (actual length / template length) are calculated, * the maximum scaling factor L is extracted, then all helix positions * are multiplied by L. * 2 - Same as 1, but L is computed as the minimum value such that there * are no backbone intersections. */ public static class DrawRNATemplateMethod { public static int NOADJUST = 0; public static int MAXSCALINGFACTOR = 1; public static int NOINTERSECT = 2; public static int HELIXTRANSLATE = 3; } /** * Draw this RNA like the given template. * The helixLengthAdjustmentMethod argument tells what to do in case * some helices are of a different length in the template and the * actual helix. See class DrawRNATemplateMethod above for possible values. */ public RNATemplateMapping drawRNATemplate( RNATemplate template, int helixLengthAdjustmentMethod) throws RNATemplateDrawingAlgorithmException { _drawn = true; _drawMode = DRAW_MODE_TEMPLATE; // debug // try { // RNA perfectMatchingRNA = template.toRNA(); // System.out.println("An RNA that would perfectly match this template would be:"); // System.out.println(perfectMatchingRNA.getStructDBN()); // } catch (ExceptionInvalidRNATemplate e) { // e.printStackTrace(); // } RNATemplateMapping mapping = RNATemplateAlign.mapRNAWithTemplate(this, template); System.out.println(mapping.showCompact(this)); // debug // RNATemplateAlign.printMapping(mapping, template, getSeq()); // try { // TreeGraphviz.treeToGraphvizPostscript(alignment, "alignment_graphviz.ps"); // } catch (IOException e) { // e.printStackTrace(); // } Iterator<RNATemplateElement> iter; double globalIncreaseFactor = 1; Map<RNATemplateHelix, Point2D.Double> translateVectors = null; if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.MAXSCALINGFACTOR) { // Compute increase factors for helices. Map<RNATemplateHelix, Double> lengthIncreaseFactor = new HashMap<RNATemplateHelix, Double>(); double maxLengthIncreaseFactor = Double.NEGATIVE_INFINITY; RNATemplateHelix maxIncreaseHelix = null; iter = template.rnaIterator(); while (iter.hasNext()) { RNATemplateElement element = iter.next(); if (element instanceof RNATemplateHelix && mapping.getAncestor(element) != null && !lengthIncreaseFactor.containsKey(element)) { RNATemplateHelix helix = (RNATemplateHelix) element; int[] basesInHelixArray = RNATemplateAlign.intArrayFromList(mapping.getAncestor(helix)); Arrays.sort(basesInHelixArray); double l = computeLengthIncreaseFactor(basesInHelixArray, helix); lengthIncreaseFactor.put(helix, l); if (l > maxLengthIncreaseFactor) { maxLengthIncreaseFactor = l; maxIncreaseHelix = helix; } } } // debug System.out.println("Max helix length increase factor = " + maxLengthIncreaseFactor + " reached with helix " + maxIncreaseHelix);; globalIncreaseFactor = Math.max(1, maxLengthIncreaseFactor); } else if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.HELIXTRANSLATE) { try { // Now we need to propagate this helices translations Tree<RNANodeValueTemplate> templateAsTree = template.toTree(); translateVectors = computeHelixTranslations(templateAsTree, mapping); } catch (ExceptionInvalidRNATemplate e) { throw (new RNATemplateDrawingAlgorithmException("ExceptionInvalidRNATemplate: " + e.getMessage())); } } // Allocate the coords and centers arrays // We create Point2D.Double objects in it but the algorithms // we use may choose to create new Point2D.Double objects or to // modify those created here. Point2D.Double[] coords = new Point2D.Double[_listeBases.size()]; Point2D.Double[] centers = new Point2D.Double[_listeBases.size()]; for (int i = 0; i < _listeBases.size(); i++) { coords[i] = new Point2D.Double(0, 0); centers[i] = new Point2D.Double(0, 0); } boolean computeCoords = true; while (computeCoords) { computeCoords = false; // Compute coords and centers Set<RNATemplateHelix> alreadyDrawnHelixes = new HashSet<RNATemplateHelix>(); RNATemplateHelix lastMappedHelix = null; EdgeEndPoint howWeGotOutOfLastHelix = null; int howWeGotOutOfLastHelixBaseIndex = 0; iter = template.rnaIterator(); RNATemplateElement element = null; while (iter.hasNext()) { element = iter.next(); if (element instanceof RNATemplateHelix && mapping.getAncestor(element) != null) { // We have a mapping between an helix in the RNA sequence // and an helix in the template. RNATemplateHelix helix = (RNATemplateHelix) element; boolean firstTimeWeMeetThisHelix; int[] basesInHelixArray = RNATemplateAlign.intArrayFromList(mapping.getAncestor(helix)); Arrays.sort(basesInHelixArray); // Draw this helix if it has not already been done if (!alreadyDrawnHelixes.contains(helix)) { firstTimeWeMeetThisHelix = true; drawHelixLikeTemplateHelix(basesInHelixArray, helix, coords, centers, globalIncreaseFactor, translateVectors); alreadyDrawnHelixes.add(helix); } else { firstTimeWeMeetThisHelix = false; } EdgeEndPoint howWeGetInCurrentHelix; if (firstTimeWeMeetThisHelix) { if (helix.getIn1Is() == In1Is.IN1_IS_5PRIME) { howWeGetInCurrentHelix = helix.getIn1(); } else { howWeGetInCurrentHelix = helix.getIn2(); } } else { if (helix.getIn1Is() == In1Is.IN1_IS_5PRIME) { howWeGetInCurrentHelix = helix.getIn2(); } else { howWeGetInCurrentHelix = helix.getIn1(); } } Point2D.Double P0 = new Point2D.Double(); Point2D.Double P3 = new Point2D.Double(); if (lastMappedHelix != null) { // Now draw the RNA sequence (possibly containing helixes) // between the last template drawn helix and this one. if (lastMappedHelix == helix) { // Last helix is the same as the current one so // nothing matched (or at best a single // non-paired sequence) so we will just // use the Radiate algorithm Point2D.Double helixVector = new Point2D.Double(); computeHelixEndPointDirections(howWeGotOutOfLastHelix, helixVector, new Point2D.Double()); double angle = angleFromVector(helixVector); int b1 = basesInHelixArray[basesInHelixArray.length/2 - 1]; P0.setLocation(coords[b1]); int b2 = basesInHelixArray[basesInHelixArray.length/2]; P3.setLocation(coords[b2]); Point2D.Double loopCenter = new Point2D.Double((P0.x + P3.x)/2, (P0.y + P3.y)/2); drawLoop(b1, b2, loopCenter.x, loopCenter.y, angle, coords, centers); // If the helix is flipped, we need to compute the symmetric // of the whole loop. if (helix.isFlipped()) { Point2D.Double[] points1 = new Point2D.Double[b2-b1+1]; Point2D.Double[] points2 = new Point2D.Double[b2-b1+1]; for (int b=b1; b<=b2; b++) { // This is an assignment by reference. points1[b-b1] = coords[b]; points2[b-b1] = centers[b]; } symmetric(loopCenter, helixVector, points1); symmetric(loopCenter, helixVector, points2); } } else { // No helices matched between the last helix and // the current one, so we draw what is between // using the radiate algorithm but on the Bezier curve. int b1 = howWeGotOutOfLastHelixBaseIndex; int b2 = firstTimeWeMeetThisHelix ? basesInHelixArray[0] : basesInHelixArray[basesInHelixArray.length/2]; P0.setLocation(coords[b1]); P3.setLocation(coords[b2]); Point2D.Double P1, P2; if (howWeGotOutOfLastHelix.getOtherElement() instanceof RNATemplateUnpairedSequence && howWeGetInCurrentHelix.getOtherElement() instanceof RNATemplateUnpairedSequence) { // We will draw the bases on a Bezier curve P1 = new Point2D.Double(); computeBezierTangentVectorTarget(howWeGotOutOfLastHelix, P0, P1); P2 = new Point2D.Double(); computeBezierTangentVectorTarget(howWeGetInCurrentHelix, P3, P2); } else { // We will draw the bases on a straight line between P0 and P3 P1 = null; P2 = null; } drawAlongCurve(b1+1, b2-1, P0, P1, P2, P3, coords, centers); } } else if (basesInHelixArray[0] > 0) { // Here we draw what is before the first mapped helix. RNATemplateUnpairedSequence templateSequence; // Try to find our template sequence as the mapped element of base 0 RNATemplateElement templateSequenceCandidate = mapping.getPartner(0); if (templateSequenceCandidate != null && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) { templateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate; } else { // Try other idea: first template element if it is a sequence templateSequenceCandidate = template.getFirst(); if (templateSequenceCandidate != null && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) { templateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate; } else { // We don't know where to start templateSequence = null; } } int b1 = 0; int b2 = firstTimeWeMeetThisHelix ? basesInHelixArray[0] : basesInHelixArray[basesInHelixArray.length/2]; P3.setLocation(coords[b2]); if (templateSequence != null) { coords[0].setLocation(templateSequence.getVertex5()); coords[0].x *= globalIncreaseFactor; coords[0].y *= globalIncreaseFactor; } else { // We start "near". // This is quite arbitrary, the user will need to manually place this bases. coords[0].setLocation(coords[b2].x, coords[b2].y + 100); } P0.setLocation(coords[0]); Point2D.Double P1, P2; if (howWeGetInCurrentHelix.getOtherElement() instanceof RNATemplateUnpairedSequence && templateSequence != null) { // We will draw the bases on a Bezier curve P1 = new Point2D.Double(); computeBezierTangentVectorTarget(templateSequence.getIn(), P0, P1); P2 = new Point2D.Double(); computeBezierTangentVectorTarget(howWeGetInCurrentHelix, P3, P2); } else { // We will draw the bases on a straight line between P0 and P3 P1 = null; P2 = null; } drawAlongCurve(b1, b2-1, P0, P1, P2, P3, coords, centers); } lastMappedHelix = helix; howWeGotOutOfLastHelix = howWeGetInCurrentHelix.getNextEndPoint(); if (firstTimeWeMeetThisHelix) { howWeGotOutOfLastHelixBaseIndex = basesInHelixArray[basesInHelixArray.length/2-1]; } else { howWeGotOutOfLastHelixBaseIndex = basesInHelixArray[basesInHelixArray.length-1]; } } } // end template iteration // Now we need to draw what is after the last mapped helix. if (howWeGotOutOfLastHelixBaseIndex < coords.length-1 && element != null && coords.length > 1) { RNATemplateUnpairedSequence beginTemplateSequence = null; if (lastMappedHelix == null) { // No helix at all matched between the template and RNA! // So the sequence we want to draw is the full RNA. // Try to find our template sequence as the mapped element of base 0 RNATemplateElement templateSequenceCandidate = mapping.getPartner(0); if (templateSequenceCandidate != null && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) { beginTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate; } else { // Try other idea: first template element if it is a sequence templateSequenceCandidate = template.getFirst(); if (templateSequenceCandidate != null && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) { beginTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate; } else { // We don't know where to start beginTemplateSequence = null; } } if (beginTemplateSequence != null) { coords[0].setLocation(beginTemplateSequence.getVertex5()); coords[0].x *= globalIncreaseFactor; coords[0].y *= globalIncreaseFactor; } } RNATemplateUnpairedSequence endTemplateSequence; // Try to find our template sequence as the mapped element of last base RNATemplateElement templateSequenceCandidate = mapping.getPartner(coords.length-1); if (templateSequenceCandidate != null && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) { endTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate; } else { // Try other idea: last template element if it is a sequence templateSequenceCandidate = element; if (templateSequenceCandidate != null && templateSequenceCandidate instanceof RNATemplateUnpairedSequence) { endTemplateSequence = (RNATemplateUnpairedSequence) templateSequenceCandidate; } else { // We don't know where to end endTemplateSequence = null; } } int b1 = howWeGotOutOfLastHelixBaseIndex; int b2 = coords.length - 1; if (endTemplateSequence != null) { coords[b2].setLocation(endTemplateSequence.getVertex3()); } else { // We end "near". // This is quite arbitrary, the user will need to manually place this bases. coords[b2].setLocation(coords[b1].x, coords[b1].y + 100); } coords[coords.length-1].x *= globalIncreaseFactor; coords[coords.length-1].y *= globalIncreaseFactor; if (lastMappedHelix == null && beginTemplateSequence == null && endTemplateSequence == null) { // We are extremely unlucky, as we haven't mapped // any helix not any half-unpaired sequence from the template! // Set last base position at something different // than (0,0) to prevent all bases to be drawn at (0,0). coords[coords.length-1].setLocation(1000, 1000); } Point2D.Double P0 = new Point2D.Double(); Point2D.Double P3 = new Point2D.Double(); P0.setLocation(coords[b1]); P3.setLocation(coords[b2]); Point2D.Double P1, P2; if (howWeGotOutOfLastHelix != null && howWeGotOutOfLastHelix.getOtherElement() instanceof RNATemplateUnpairedSequence && endTemplateSequence != null) { // We will draw the bases on a Bezier curve P1 = new Point2D.Double(); computeBezierTangentVectorTarget(howWeGotOutOfLastHelix, P0, P1); P2 = new Point2D.Double(); computeBezierTangentVectorTarget(endTemplateSequence.getOut(), P3, P2); } else if (lastMappedHelix == null && beginTemplateSequence != null && endTemplateSequence != null) { // We will draw the bases on a Bezier curve P1 = new Point2D.Double(); computeBezierTangentVectorTarget(beginTemplateSequence.getIn(), P0, P1); P2 = new Point2D.Double(); computeBezierTangentVectorTarget(endTemplateSequence.getOut(), P3, P2); } else { // We will draw the bases on a straight line between P0 and P3 P1 = null; P2 = null; } drawAlongCurve((lastMappedHelix != null ? b1+1 : b1), b2, P0, P1, P2, P3, coords, centers); } if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.NOINTERSECT && coords.length > 3) { // Are we happy with this value of globalIncreaseFactor? Line2D.Double[] lines = new Line2D.Double[coords.length-1]; for (int i=0; i<coords.length-1; i++) { lines[i] = new Line2D.Double(coords[i], coords[i+1]); } int intersectLines = 0; for (int i=0; i<lines.length; i++) { for (int j=i+2; j<lines.length; j++) { if (lines[i].intersectsLine(lines[j])) { intersectLines++; } } } // If no intersection we keep this globalIncreaseFactor value if (intersectLines > 0) { // Don't increase more than a maximum value if (globalIncreaseFactor < 3) { globalIncreaseFactor += 0.1; System.out.println("globalIncreaseFactor increased to " + globalIncreaseFactor); // Compute the drawing again computeCoords = true; } } } } // debug if (helixLengthAdjustmentMethod == DrawRNATemplateMethod.MAXSCALINGFACTOR || helixLengthAdjustmentMethod == DrawRNATemplateMethod.NOINTERSECT) { System.out.println("globalIncreaseFactor = " + globalIncreaseFactor); } // Now we actually move the bases, according to arrays coords and centers // and taking in account the space between bases parameter. for (int i = 0; i < _listeBases.size(); i++) { _listeBases.get(i).setCoords( new Point2D.Double(coords[i].x * _spaceBetweenBases, coords[i].y * _spaceBetweenBases)); _listeBases.get(i).setCenter( new Point2D.Double(centers[i].x * _spaceBetweenBases, centers[i].y * _spaceBetweenBases)); } return mapping; } private static double objFun(int n1, int n2, double r, double bpdist, double multidist) { return (((double) n1) * 2.0 * Math.asin(((double) bpdist) / (2.0 * r)) + ((double) n2) * 2.0 * Math.asin(((double) multidist) / (2.0 * r)) - (2.0 * Math.PI)); } public double determineRadius(int nbHel, int nbUnpaired, double startRadius) { return determineRadius(nbHel,nbUnpaired,startRadius,BASE_PAIR_DISTANCE,MULTILOOP_DISTANCE); } public static double determineRadius(int nbHel, int nbUnpaired, double startRadius, double bpdist, double multidist) { double xmin = bpdist / 2.0; double xmax = 3.0 * multidist + 1; double x = (xmin + xmax) / 2.0; double y = 10000.0; double ymin = -1000.0; double ymax = 1000.0; int numIt = 0; double precision = 0.00001; while ((Math.abs(y) > precision) && (numIt < 10000)) { x = (xmin + xmax) / 2.0; y = objFun(nbHel, nbUnpaired, x, bpdist, multidist); ymin = objFun(nbHel, nbUnpaired, xmax, bpdist, multidist); ymax = objFun(nbHel, nbUnpaired, xmin, bpdist, multidist); if (ymin > 0.0) { xmax = xmax + (xmax - xmin); } else if ((y <= 0.0) && (ymax > 0.0)) { xmax = x; } else if ((y >= 0.0) && (ymin < 0.0)) { xmin = x; } else if (ymax < 0.0) { xmin = Math.max(xmin - (x - xmin), Math.max( bpdist / 2.0, multidist / 2.0)); xmax = x; } numIt++; } return x; } public void drawRNA(VARNAConfig conf) throws ExceptionNAViewAlgorithm { drawRNA(RNA.DEFAULT_DRAW_MODE,conf); } public void drawRNA(int mode,VARNAConfig conf) throws ExceptionNAViewAlgorithm { _drawMode = mode; switch (get_drawMode()) { case RNA.DRAW_MODE_RADIATE: drawRNARadiate(conf); break; case RNA.DRAW_MODE_LINEAR: drawRNALine(); break; case RNA.DRAW_MODE_CIRCULAR: drawRNACircle(); break; case RNA.DRAW_MODE_NAVIEW: drawRNANAView(); break; case RNA.DRAW_MODE_VARNA_VIEW: drawRNAVARNAView(); break; case RNA.DRAW_MODE_MOTIFVIEW: drawMOTIFView(); break; default: break; } } public int getDrawMode() { return _drawMode; } private void drawLoop(int i, int j, double x, double y, double dirAngle, Point2D.Double[] coords, Point2D.Double[] centers) { if (i > j) { return; } // BasePaired if (_listeBases.get(i).getElementStructure() == j) { double normalAngle = Math.PI / 2.0; centers[i] = new Point2D.Double(x, y); centers[j] = new Point2D.Double(x, y); coords[i].x = (x + BASE_PAIR_DISTANCE * Math.cos(dirAngle - normalAngle) / 2.0); coords[i].y = (y + BASE_PAIR_DISTANCE * Math.sin(dirAngle - normalAngle) / 2.0); coords[j].x = (x + BASE_PAIR_DISTANCE * Math.cos(dirAngle + normalAngle) / 2.0); coords[j].y = (y + BASE_PAIR_DISTANCE * Math.sin(dirAngle + normalAngle) / 2.0); drawLoop(i + 1, j - 1, x + LOOP_DISTANCE * Math.cos(dirAngle), y + LOOP_DISTANCE * Math.sin(dirAngle), dirAngle, coords, centers); } else { int k = i; Vector<Integer> basesMultiLoop = new Vector<Integer>(); Vector<Integer> helices = new Vector<Integer>(); int l; while (k <= j) { l = _listeBases.get(k).getElementStructure(); if (l > k) { basesMultiLoop.add(new Integer(k)); basesMultiLoop.add(new Integer(l)); helices.add(new Integer(k)); k = l + 1; } else { basesMultiLoop.add(new Integer(k)); k++; } } int mlSize = basesMultiLoop.size() + 2; int numHelices = helices.size() + 1; double totalLength = MULTILOOP_DISTANCE * (mlSize - numHelices) + BASE_PAIR_DISTANCE * numHelices; double multiLoopRadius; double angleIncrementML; double angleIncrementBP; if (mlSize > 3) { multiLoopRadius = determineRadius(numHelices, mlSize - numHelices, (totalLength) / (2.0 * Math.PI), BASE_PAIR_DISTANCE, MULTILOOP_DISTANCE ); angleIncrementML = -2.0 * Math.asin(((float) MULTILOOP_DISTANCE) / (2.0 * multiLoopRadius)); angleIncrementBP = -2.0 * Math.asin(((float) BASE_PAIR_DISTANCE) / (2.0 * multiLoopRadius)); } else { multiLoopRadius = 35.0; angleIncrementBP = -2.0 * Math.asin(((float) BASE_PAIR_DISTANCE) / (2.0 * multiLoopRadius)); angleIncrementML = (-2.0 * Math.PI - angleIncrementBP) / 2.0; } // System.out.println("MLr:"+multiLoopRadius+" iBP:"+angleIncrementBP+" iML:"+angleIncrementML); double centerDist = Math.sqrt(Math.max(Math.pow(multiLoopRadius, 2) - Math.pow(BASE_PAIR_DISTANCE / 2.0, 2), 0.0)) - LOOP_DISTANCE; Point2D.Double mlCenter = new Point2D.Double( (x + (centerDist * Math.cos(dirAngle))), (y + (centerDist * Math.sin(dirAngle)))); // Base directing angle for (multi|hairpin) loop, from the center's // perspective double baseAngle = dirAngle // U-turn + Math.PI // Account for already drawn supporting base-pair + 0.5 * angleIncrementBP // Base cannot be paired twice, so next base is at // "unpaired base distance" + 1.0 * angleIncrementML; double[] angles = new double[_listeBases.size()]; int n1 = 1; int n2 = 1; for (k = basesMultiLoop.size() - 1; k >= 0; k--) { l = basesMultiLoop.get(k).intValue(); centers[l] = mlCenter; angles[l] = baseAngle; coords[l].x = mlCenter.x + multiLoopRadius * Math.cos(baseAngle); coords[l].y = mlCenter.y + multiLoopRadius * Math.sin(baseAngle); if ((_listeBases.get(l).getElementStructure() < l) && (_listeBases.get(l).getElementStructure() != -1)) { baseAngle += angleIncrementBP; n1++; } else { baseAngle += angleIncrementML; n2++; } } // System.out.println("n1:"+n1+" n2:"+n2); double newAngle; int m, n; for (k = 0; k < helices.size(); k++) { m = helices.get(k).intValue(); n = _listeBases.get(m).getElementStructure(); newAngle = (angles[m] + angles[n]) / 2.0; drawLoop(m + 1, n - 1, (LOOP_DISTANCE * Math.cos(newAngle)) + (coords[m].x + coords[n].x) / 2.0, (LOOP_DISTANCE * Math.sin(newAngle)) + (coords[m].y + coords[n].y) / 2.0, newAngle, coords, centers); } } } public void drawRNARadiate(VARNAConfig conf) { drawRNARadiate(-1.0,conf._flatExteriorLoop); } public void drawRNARadiate(double dirAngle, boolean flatExteriorLoop) { _drawn = true; _drawMode = DRAW_MODE_RADIATE; Point2D.Double[] coords = new Point2D.Double[_listeBases.size()]; Point2D.Double[] centers = new Point2D.Double[_listeBases.size()]; for (int i = 0; i < _listeBases.size(); i++) { coords[i] = new Point2D.Double(0, 0); centers[i] = new Point2D.Double(0, 0); } if (flatExteriorLoop) { dirAngle += 1.0 - Math.PI/2.0; int i=0; double x = 0.0; double y = 0.0; double vx = -Math.sin(dirAngle); double vy = Math.cos(dirAngle); while(i<_listeBases.size()) { coords[i].x = x; coords[i].y = y; centers[i].x = x+BASE_PAIR_DISTANCE*vy; centers[i].y = y-BASE_PAIR_DISTANCE*vx; int j = _listeBases.get(i).getElementStructure(); if (j>i) { drawLoop(i, j, x+(BASE_PAIR_DISTANCE*vx/2.0), y+(BASE_PAIR_DISTANCE*vy/2.0), dirAngle, coords, centers); centers[i].x = coords[i].x+BASE_PAIR_DISTANCE*vy; centers[i].y = y-BASE_PAIR_DISTANCE*vx; i = j; x += BASE_PAIR_DISTANCE*vx; y += BASE_PAIR_DISTANCE*vy; centers[i].x = coords[i].x+BASE_PAIR_DISTANCE*vy; centers[i].y = y-BASE_PAIR_DISTANCE*vx; } x += MULTILOOP_DISTANCE*vx; y += MULTILOOP_DISTANCE*vy; i += 1; } } else { drawLoop(0, _listeBases.size() - 1, 0, 0, dirAngle, coords, centers); } for (int i = 0; i < _listeBases.size(); i++) { _listeBases.get(i).setCoords( new Point2D.Double(coords[i].x * _spaceBetweenBases, coords[i].y * _spaceBetweenBases)); _listeBases.get(i).setCenter( new Point2D.Double(centers[i].x * _spaceBetweenBases, centers[i].y * _spaceBetweenBases)); } // TODO // change les centres des bases de la premiere helice vers la boucle la // plus proche } public void drawRNANAView() throws ExceptionNAViewAlgorithm { _drawMode = DRAW_MODE_NAVIEW; _drawn = true; ArrayList<Double> X = new ArrayList<Double>(_listeBases.size()); ArrayList<Double> Y = new ArrayList<Double>(_listeBases.size()); ArrayList<Short> pair_table = new ArrayList<Short>(_listeBases.size()); for (int i = 0; i < _listeBases.size(); i++) { pair_table.add(Short.valueOf(String.valueOf(_listeBases.get(i) .getElementStructure()))); } NAView naView = new NAView(); naView.naview_xy_coordinates(pair_table, X, Y); // Updating individual base positions for (int i = 0; i < _listeBases.size(); i++) { _listeBases.get(i).setCoords( new Point2D.Double(X.get(i) * 2.5 * _spaceBetweenBases, Y .get(i) * 2.5 * _spaceBetweenBases)); } // Updating centers for (int i = 0; i < _listeBases.size(); i++) { int indicePartner = _listeBases.get(i).getElementStructure(); if (indicePartner != -1) { Point2D.Double base = _listeBases.get(i).getCoords(); Point2D.Double partner = _listeBases.get(indicePartner) .getCoords(); _listeBases.get(i).setCenter( new Point2D.Double((base.x + partner.x) / 2.0, (base.y + partner.y) / 2.0)); } else { Vector<Integer> loop = getLoopBases(i); double tmpx = 0.0; double tmpy = 0.0; for (int j = 0; j < loop.size(); j++) { int partner = loop.elementAt(j); Point2D.Double loopmember = _listeBases.get(partner) .getCoords(); tmpx += loopmember.x; tmpy += loopmember.y; } _listeBases.get(i).setCenter( new Point2D.Double(tmpx / loop.size(), tmpy / loop.size())); } } } public void drawMOTIFView() { _drawn = true; _drawMode = DRAW_MODE_MOTIFVIEW; int spaceBetweenStrand =0; Motif motif = new Motif(this,get_listeBases()); motif.listStrand(); for (int i = 0; i < motif.getListStrand().sizeStruct(); i++ ){ for (int j = 0; j < motif.getListStrand().getStrand(i).sizeStrand(); j++ ){ int indice = motif.getListStrand().getStrand(i).getMB(j).getIndex(); get_listeBases().get(indice).setCoords( new Point2D.Double(0,0)); get_listeBases().get(indice).setCenter( new Point2D.Double(0, 0)); } } //Recherche du brin central int centralStrand = motif.getCentralStrand(); //Cas o l'on a un motif en toile if(centralStrand!=-1){ //On positionne le brin central motif.positionneSpecificStrand(centralStrand, spaceBetweenStrand); //On place les autres brins par rapport a ce brin central motif.orderStrands(centralStrand); } else { centralStrand = 0; motif.positionneStrand(); motif.ajusteStrand(); } motif.reajustement(); motif.deviationBasePair(); motif.setCenterMotif(); } public ArrayList<ModeleBase> getAllPartners(int indice) { ArrayList<ModeleBase> result = new ArrayList<ModeleBase>(); ModeleBase me = this.getBaseAt(indice); int i = me.getElementStructure(); if (i!= -1) { result.add(getBaseAt(i)); } ArrayList<ModeleBP> msbps = getAuxBPs(indice); for (ModeleBP m : msbps) { result.add(m.getPartner(me)); } return result; } public int get_drawMode() { return _drawMode; } public void setDrawMode(int drawMode) { _drawMode = drawMode; } public void setRNA(String seq, String str) throws ExceptionFileFormatOrSyntax, ExceptionUnmatchedClosingParentheses { setRNA(RNA.explodeSequence(seq), str); } public void setRNA(String[] seq, int[] str) throws ExceptionFileFormatOrSyntax { setRNA(seq, str, 1); } public void setRNA(List<String> seq, int[] str) throws ExceptionFileFormatOrSyntax { setRNA(seq.toArray(new String[seq.size()]), str, 1); } public void setRNA(List<String> seq, int[] str, int baseIndex) throws ExceptionFileFormatOrSyntax { setRNA(seq.toArray(new String[seq.size()]), str, baseIndex); } public void setRNA(String[] seq, int[] str, int baseIndex) throws ExceptionFileFormatOrSyntax { clearAnnotations(); _listeBases = new ArrayList<ModeleBase>(); if (seq.length != str.length) { warningEmition("Sequence length " + seq.length + " differs from that of secondary structure " + str.length + ". \nAdapting sequence length ..."); if (seq.length < str.length) { String [] nseq = new String[str.length]; for(int i=0;i<seq.length;i++) { nseq[i] = seq[i]; } for(int i=seq.length;i<nseq.length;i++) { nseq[i] = ""; } seq = nseq; } else { String[] seqTmp = new String[str.length]; for (int i =0;i<str.length;i++) { seqTmp[i] = seq[i]; } seq = seqTmp; } } for (int i = 0; i < str.length; i++) { _listeBases.add(new ModeleBaseNucleotide(seq[i], i, baseIndex+i)); } applyStruct(str); } /** * Sets the RNA to be drawn. Uses when comparison mode is on. Will draw the * super-structure passed in parameters and apply specials styles to the * bases owning by each RNA alignment and both. * * @param seq * - The sequence of the super-structure This sequence shall be * designed like this: * <code>firstRNA1stBaseSecondRNA1stBaseFirstRNA2ndBaseSecondRNA2ndBase [...]</code> * <br> * <b>Example:</b> <code>AAC-GUAGA--UGG</code> * @param struct * - The super-structure * @param basesOwn * - The RNA owning bases array (each index will be:0 when common * base, 1 when first RNA alignment base, 2 when second RNA * alignment base) * @throws ExceptionUnmatchedClosingParentheses * @throws ExceptionFileFormatOrSyntax */ public void setRNA(String seq, String struct, ArrayList<Integer> basesOwn) throws ExceptionUnmatchedClosingParentheses, ExceptionFileFormatOrSyntax { clearAnnotations(); _listeBases = new ArrayList<ModeleBase>(); // On "parse" la structure (repérage des points, tiret et couples // parentheses ouvrante/fermante) int[] array_struct = parseStruct(struct); int size = struct.length(); int j = 0; for (int i = 0; i < size; i++) { ModeleBase mb; if (seq.charAt(j)!=seq.charAt(j + 1)) { ModeleBasesComparison mbc = new ModeleBasesComparison(seq.charAt(j), seq.charAt(j + 1), i); mbc.set_appartenance(basesOwn.get(i)); mbc.setBaseNumber(i+1); mb = mbc; } else { mb = new ModeleBaseNucleotide(""+seq.charAt(j),i,i+1); } _listeBases.add(mb); j+=2; } for (int i = 0; i < size; i++) { if (array_struct[i]!=-1) {this.addBP(i, array_struct[i]);} j += 2; } } public void setRNA(List<String> seq, String dbnStr) throws ExceptionUnmatchedClosingParentheses, ExceptionFileFormatOrSyntax { clearAnnotations(); String parDBN = dbnStr.replace('(', '(').replace(')', ')').replace('[', ':').replace(']', ':').replace('{', ':').replace('}', ':').replace('<', ':').replace('>', ':'); String braDBN = dbnStr.replace('(', ':').replace(')', ':').replace('[', '(').replace(']', ')').replace('{', ':').replace('}', ':').replace('<', ':').replace('>', ':'); String accDBN = dbnStr.replace('(', ':').replace(')', ':').replace('[', ':').replace(']', ':').replace('{', '(').replace('}', ')').replace('<', ':').replace('>', ':'); String cheDBN = dbnStr.replace('(', ':').replace(')', ':').replace('[', ':').replace(']', ':').replace('{', ':').replace('}', ':').replace('<', '(').replace('>', ')'); int[] parStr = parseStruct(parDBN); int[] braStr = parseStruct(braDBN); int[] accStr = parseStruct(accDBN); int[] cheStr = parseStruct(cheDBN); int[] finStr = new int[parStr.length]; for(int i=0;i<parStr.length;i++) { finStr[i] = -1; } for(int i=0;i<parStr.length;i++) { if (parStr[i]>i) { finStr[i] = parStr[i]; finStr[finStr[i]] = i; } else if (braStr[i]>i) { if ((parStr[i]==-1)&&(parStr[braStr[i]]==-1)) { finStr[i] = braStr[i]; finStr[finStr[i]] = i; } } else if (accStr[i]>i) { if ((parStr[i]==-1)&&(parStr[accStr[i]]==-1)&&(braStr[i]==-1)&&(braStr[accStr[i]]==-1)) { finStr[i] = accStr[i]; finStr[finStr[i]] = i; } } else if (cheStr[i]>i) { if ((parStr[i]==-1)&&(parStr[cheStr[i]]==-1)&&(braStr[i]==-1)&&(braStr[cheStr[i]]==-1)&&(accStr[i]==-1)&&(accStr[cheStr[i]]==-1)) { finStr[i] = cheStr[i]; finStr[cheStr[i]] = i; } } } setRNA(seq, finStr); } public static ArrayList<String> explodeSequence(String seq) { ArrayList<String> analyzedSeq = new ArrayList<String>(); int i=0; while(i<seq.length()) { if (seq.charAt(i)=='{') { boolean found = false; String buf = ""; i++; while(!found & (i<seq.length())) { if (seq.charAt(i)!='}') { buf += seq.charAt(i); i++; } else { found = true;} } analyzedSeq.add(buf); } else { analyzedSeq.add(""+seq.charAt(i)); } i++; } return analyzedSeq; } public int[] parseStruct(String str) throws ExceptionUnmatchedClosingParentheses, ExceptionFileFormatOrSyntax { int[] result = new int[str.length()]; int unexpectedChar = -1; Stack<Integer> p = new Stack<Integer>(); for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (c == '(') { p.push(new Integer(i)); } else if (c == '.' || c == '-' || c == ':') { result[i] = -1; } else if (c == ')') { if (p.size() == 0) { throw new ExceptionUnmatchedClosingParentheses(i + 1); } int j = p.pop().intValue(); result[i] = j; result[j] = i; } else { if (unexpectedChar == -1) unexpectedChar = i; break; } } if (unexpectedChar != -1) { //warningEmition("Unexpected Character at index:" + unexpectedChar); } if (p.size() != 0) { throw new ExceptionUnmatchedClosingParentheses( p.pop().intValue() + 1); } return result; } public Point getHelixInterval(int index) { if ((index < 0) || (index >= _listeBases.size())) { return new Point(index, index); } int j = _listeBases.get(index).getElementStructure(); if (j != -1) { int minH = index; int maxH = index; if (j > index) { maxH = j; } else { minH = j; } boolean over = false; while (!over) { if ((minH < 0) || (maxH >= _listeBases.size())) { over = true; } else { if (_listeBases.get(minH).getElementStructure() == maxH) { minH--; maxH++; } else { over = true; } } } minH++; maxH--; return new Point(minH, maxH); } return new Point(0, 0); } public ArrayList<Integer> getHelix(int index) { ArrayList<Integer> result = new ArrayList<Integer>(); if ((index < 0) || (index >= _listeBases.size())) { return result; } Point p = getHelixInterval(index); for (int i =p.x;i<=p.y;i++) { result.add(i); result.add(this._listeBases.get(i).getElementStructure()); } return result; } public Point getMultiLoop(int index) { if ((index < 0) || (index >= _listeBases.size())) { return new Point(index, index); } Point h = getHelixInterval(index); int minH = h.x - 1; int maxH = h.y + 1; boolean over = false; while (!over) { if (minH < 0) { over = true; minH = 0; } else { if (_listeBases.get(minH).getElementStructure() == -1) { minH--; } else if (_listeBases.get(minH).getElementStructure() < minH) { minH = _listeBases.get(minH).getElementStructure() - 1; } else { over = true; } } } over = false; while (!over) { if (maxH > _listeBases.size() - 1) { over = true; maxH = _listeBases.size() - 1; } else { if (_listeBases.get(maxH).getElementStructure() == -1) { maxH++; } else if (_listeBases.get(maxH).getElementStructure() > maxH) { maxH = _listeBases.get(maxH).getElementStructure() + 1; } else { over = true; } } } return new Point(minH, maxH); } public Vector<Integer> getLoopBases(int startIndex) { Vector<Integer> result = new Vector<Integer>(); if ((startIndex < 0) || (startIndex >= _listeBases.size())) { return result; } int index = startIndex; result.add(startIndex); if (_listeBases.get(index).getElementStructure() <= index) { index = (index + 1) % _listeBases.size(); } else { index = _listeBases.get(index).getElementStructure(); result.add(index); index = (index + 1) % _listeBases.size(); } while (index != startIndex) { result.add(index); if (_listeBases.get(index).getElementStructure() == -1) { index = (index + 1) % _listeBases.size(); } else { index = _listeBases.get(index).getElementStructure(); result.add(index); index = (index + 1) % _listeBases.size(); } } return result; } /** * Returns the RNA secondary structure displayed by this panel as a * well-parenthesized word, accordingly to the DBN format * * @return This panel's secondary structure */ public String getStructDBN() { String result = ""; for (int i = 0; i < _listeBases.size(); i++) { int j = _listeBases.get(i).getElementStructure(); if (j == -1) { result += "."; } else if (i > j) { result += ")"; } else { result += "("; } } return result; } public ArrayList<int[]> paginateStructure() { ArrayList<int[]> result = new ArrayList<int[]>(); // Mumbo jumbo to sort the basepair list ArrayList<ModeleBP> bps = this.getAllBPs(); ModeleBP[] mt = new ModeleBP[bps.size()]; bps.toArray(mt); Arrays.sort(mt, new Comparator<ModeleBP>(){ public int compare(ModeleBP arg0, ModeleBP arg1) { if (arg0.getIndex5()!=arg1.getIndex5()) return arg0.getIndex5()-arg1.getIndex5(); else return arg0.getIndex3()-arg1.getIndex3(); }}); bps = new ArrayList<ModeleBP>(); for(int i=0;i<mt.length;i++) { bps.add(mt[i]); } while (bps.size()!=0) { ArrayList<ModeleBP> currentBPs = new ArrayList<ModeleBP>(); Stack<Integer> pile = new Stack<Integer>(); int[] ss = new int[this.getSize()]; for (int i=0;i<ss.length;i++) { ss[i] = -1; } for(int i=0;i<bps.size();i++) { ModeleBP bp = bps.get(i); boolean ok = true; if (!pile.empty()) { int x = pile.peek(); if ((bp.getIndex5()<=x && bp.getIndex3()>=x) || (ss[bp.getIndex5()]!=-1)) { ok = false; } } if (ok) { ss[bp.getIndex5()] = bp.getIndex3(); ss[bp.getIndex3()] = bp.getIndex5(); currentBPs.add(bp); pile.add(bp.getIndex3()); } if (!pile.empty() && (i==pile.peek())) { pile.pop(); } } bps.removeAll(currentBPs); result.add(ss); } return result; } public String getStructDBN(boolean includeMostPKs) { String result = getStructDBN(); if (includeMostPKs) { ArrayList<int[]> pages = paginateStructure(); char[] res = new char[getSize()]; for (int i=0;i<res.length;i++) { res[i] = '.'; } System.out.println(pages.size()); char[] open = {'(','[','{','<'}; char[] close = {')',']','}','>'}; for (int p=0;p<Math.min(pages.size(),open.length);p++) { int[] page = pages.get(p); for (int i=0;i<res.length;i++) { if (page[i]!=-1 && page[i]>i && res[i]=='.' && res[page[i]]=='.') { res[i]=open[p]; res[page[i]]=close[p]; } } } result = ""; for (int i=0;i<res.length;i++) { result += res[i]; } } return result; } public String getStructDBN(int[] str) { String result = ""; for (int i = 0; i < str.length; i++) { if (str[i] == -1) { result += "."; } else if (str[i] > i) { result += "("; } else { result += ")"; } } return result; } /** * Returns the raw nucleotides sequence for the displayed RNA * * @return The RNA sequence */ public String getSeq() { String result = ""; for (int i = 0; i < _listeBases.size(); i++) { result += ((ModeleBase) _listeBases.get(i)).getContent(); } return result; } public String getStructBPSEQ() { String result = ""; int[] str = getNonOverlappingStruct(); for (int i = 0; i < _listeBases.size(); i++) { result += (i + 1) + " " + ((ModeleBaseNucleotide) _listeBases.get(i)).getContent() + " " + (str[i] + 1) + "\n"; } return result; } public int[] getNonCrossingStruct() { int[] result = new int[_listeBases.size()]; // Adding "planar" base-pairs for (int i = 0; i < _listeBases.size(); i++) { result[i] = _listeBases.get(i).getElementStructure(); } return result; } public int[] getNonOverlappingStruct() { int[] result = getNonCrossingStruct(); // Adding additional base pairs when possible (No more than one base-pair per base) for (int i = 0; i < _structureAux.size(); i++) { ModeleBP msbp = _structureAux.get(i); ModeleBase mb5 = msbp.getPartner5(); ModeleBase mb3 = msbp.getPartner3(); int j5 = mb5.getIndex(); int j3 = mb3.getIndex(); if ((result[j3]==-1) && (result[j5]==-1)) { result[j3] = j5; result[j5] = j3; } } return result; } public String getStructCT() { String result = ""; for (int i = 0; i < _listeBases.size(); i++) { result += (i + 1) + " " + ((ModeleBase) _listeBases.get(i)) .getContent() + " " + i + " " + (i + 2) + " " + (_listeBases.get(i).getElementStructure() + 1) + " " + (i + 1) + "\n"; } return result; } public void saveAsBPSEQ(String path, String title) throws ExceptionExportFailed, ExceptionPermissionDenied { try { FileWriter f = new FileWriter(path); f.write("# " + title + "\n"); f.write(this.getStructBPSEQ() + "\n"); f.close(); } catch (IOException e) { throw new ExceptionExportFailed(e.getMessage(), path); } } public void saveAsCT(String path, String title) throws ExceptionExportFailed, ExceptionPermissionDenied { try { FileWriter f = new FileWriter(path); f.write("" + _listeBases.size() + " " + title + "\n"); f.write(this.getStructCT() + "\n"); f.close(); } catch (IOException e) { throw new ExceptionExportFailed(e.getMessage(), path); } } public void saveAsDBN(String path, String title) throws ExceptionExportFailed, ExceptionPermissionDenied { try { FileWriter f = new FileWriter(path); f.write("> " + title + "\n"); f.write(getListeBasesToString() + "\n"); f.write(getStructDBN() + "\n"); f.close(); } catch (IOException e) { throw new ExceptionExportFailed(e.getMessage(), path); } } public String getListeBasesToString() { String s = new String(); for (int i = 0; i < _listeBases.size(); i++) { s += ((ModeleBaseNucleotide) _listeBases.get(i)).getContent(); } return s; } public void applyBPs(ArrayList<ModeleBP> allbps) { ArrayList<ModeleBP> planar = new ArrayList<ModeleBP>(); ArrayList<ModeleBP> others = new ArrayList<ModeleBP>(); //System.err.println("Sequence: "+this.getSeq()); RNAMLParser.planarize(allbps, planar, others, getSize()); //System.err.println("All:"+allbps); //System.err.println("=> Planar: "+planar); //System.err.println("=> Others: "+others); for (ModeleBP mb :planar) { addBP(mb.getPartner5().getIndex(), mb.getPartner3().getIndex(), mb); } for (ModeleBP mb :others) { addBPAux(mb.getPartner5().getIndex(), mb.getPartner3().getIndex(), mb); } } public void set_listeBases(ArrayList<ModeleBase> _liste) { this._listeBases = _liste; } public void addVARNAListener(InterfaceVARNAListener rl) { _listeVARNAListener.add(rl); } public void warningEmition(String warningMessage) { for (int i = 0; i < _listeVARNAListener.size(); i++) { _listeVARNAListener.get(i).onWarningEmitted(warningMessage); } } public void applyStyleOnBases(ArrayList<Integer> basesList, ModeleStyleBase style) { for (int i = 1; i < basesList.size(); i++) { _listeBases.get(basesList.get(i)).setStyleBase(style); } } private int[] correctReciprocity(int[] str) { int[] result = new int[str.length]; for (int i = 0; i < str.length; i++) { if (str[i] != -1) { if (i == str[str[i]]) { result[i] = str[i]; } else { str[str[i]] = i; } } else { result[i] = -1; } } return result; } private void applyStruct(int[] str) throws ExceptionFileFormatOrSyntax { str = correctReciprocity(str); int[] planarSubset = RNAMLParser.planarize(str); _structureAux.clear(); for (int i = 0; i < planarSubset.length; i++) { if (str[i]>i) { if (planarSubset[i] > i) { addBP(i,planarSubset[i]); } else if ((planarSubset[i]!=str[i])) { addBPAux(i,str[i]); } } } } public ArrayList<ModeleBase> get_listeBases() { return _listeBases; } public int getSize() { return _listeBases.size(); } public ArrayList<Integer> findAll() { ArrayList<Integer> listAll = new ArrayList<Integer>(); for (int i = 0; i < get_listeBases().size(); i++) { listAll.add(i); } return listAll; } public ArrayList<Integer> findBulge(int index) { ArrayList<Integer> listUp = new ArrayList<Integer>(); if (get_listeBases().get(index).getElementStructure() == -1) { int i = index; boolean over = false; while ((i < get_listeBases().size()) && !over) { int j = get_listeBases().get(i).getElementStructure(); if (j == -1) { listUp.add(i); i++; } else { over = true; } } i = index - 1; over = false; while ((i >= 0) && !over) { int j = get_listeBases().get(i).getElementStructure(); if (j == -1) { listUp.add(i); i--; } else { over = true; } } } return listUp; } public ArrayList<Integer> findStem(int index) { ArrayList<Integer> listUp = new ArrayList<Integer>(); int i = index; do { listUp.add(i); int j = get_listeBases().get(i).getElementStructure(); if (j == -1) { i = (i + 1) % getSize(); } else { if ((j < i) && (index <= i) && (j <= index)) { i = j; } else { i = (i + 1) % getSize(); } } } while (i != index); return listUp; } public int getHelixCountOnLoop(int indice) { int cptHelice = 0; if (indice < 0 || indice >= get_listeBases().size()) return cptHelice; int i = indice; int j = get_listeBases().get(i).getElementStructure(); // Only way to distinguish "supporting base-pair" from others boolean justJumped = false; if ((j != -1) && (j < i)) { i = j + 1; indice = i; } do { j = get_listeBases().get(i).getElementStructure(); if ((j != -1) && (!justJumped)) { i = j; justJumped = true; cptHelice++; } else { i = (i + 1) % get_listeBases().size(); justJumped = false; } } while (i != indice); return cptHelice; } public ArrayList<Integer> findLoop(int indice) { return findLoopForward(indice); } public ArrayList<Integer> findLoopForward(int indice) { ArrayList<Integer> base = new ArrayList<Integer>(); if (indice < 0 || indice >= get_listeBases().size()) return base; int i = indice; int j = get_listeBases().get(i).getElementStructure(); // Only way to distinguish "supporting base-pair" from others boolean justJumped = false; if (j != -1) { i = Math.min(i, j) + 1; indice = i; } do { base.add(i); j = get_listeBases().get(i).getElementStructure(); if ((j != -1) && (!justJumped)) { i = j; justJumped = true; } else { i = (i + 1) % get_listeBases().size(); justJumped = false; } } while (i != indice); return base; } public ArrayList<Integer> findPair(int indice) { ArrayList<Integer> base = new ArrayList<Integer>(); int j = get_listeBases().get(indice).getElementStructure(); if (j != -1) { base.add(Math.min(indice, j)); base.add(Math.max(indice, j)); } return base; } public ArrayList<Integer> findLoopBackward(int indice) { ArrayList<Integer> base = new ArrayList<Integer>(); if (indice < 0 || indice >= get_listeBases().size()) return base; int i = indice; int j = get_listeBases().get(i).getElementStructure(); // Only way to distinguish "supporting base-pair" from others boolean justJumped = false; if (j != -1) { i = Math.min(i, j) - 1; indice = i; } if (i < 0) { return base; } do { base.add(i); j = get_listeBases().get(i).getElementStructure(); if ((j != -1) && (!justJumped)) { i = j; justJumped = true; } else { i = (i + get_listeBases().size() - 1) % get_listeBases().size(); justJumped = false; } } while (i != indice); return base; } public ArrayList<Integer> findHelix(int indice) { ArrayList<Integer> list = new ArrayList<Integer>(); if (get_listeBases().get(indice).getElementStructure() != -1) { list.add(indice); list.add(get_listeBases().get(indice).getElementStructure()); int i = 1, prec = get_listeBases().get(indice) .getElementStructure(); while (indice + i < get_listeBases().size() && get_listeBases().get(indice + i).getElementStructure() != -1 && get_listeBases().get(indice + i).getElementStructure() == prec - 1) { list.add(indice + i); list.add(get_listeBases().get(indice + i) .getElementStructure()); prec = get_listeBases().get(indice + i).getElementStructure(); i++; } i = -1; prec = get_listeBases().get(indice).getElementStructure(); while (indice + i >= 0 && get_listeBases().get(indice + i).getElementStructure() != -1 && get_listeBases().get(indice + i).getElementStructure() == prec + 1) { list.add(indice + i); list.add(get_listeBases().get(indice + i) .getElementStructure()); prec = get_listeBases().get(indice + i).getElementStructure(); i--; } } return list; } public ArrayList<Integer> find3Prime(int indice) { ArrayList<Integer> list = new ArrayList<Integer>(); boolean over = false; while ((indice >= 0) && !over) { over = (get_listeBases().get(indice).getElementStructure() != -1); indice--; } indice++; if (over) { indice++; } for (int i = indice; i < get_listeBases().size(); i++) { list.add(i); if (get_listeBases().get(i).getElementStructure() != -1) { return new ArrayList<Integer>(); } } return list; } public ArrayList<Integer> find5Prime(int indice) { ArrayList<Integer> list = new ArrayList<Integer>(); for (int i = 0; i <= indice; i++) { list.add(i); if (get_listeBases().get(i).getElementStructure() != -1) { return new ArrayList<Integer>(); } } return list; } public Double get_spaceBetweenBases() { return _spaceBetweenBases; } public void set_spaceBetweenBases(Double betweenBases) { _spaceBetweenBases = betweenBases; } public static Double angle(Point2D.Double p1, Point2D.Double p2, Point2D.Double p3) { Double alpha = Math.atan2(p1.y - p2.y, p1.x - p2.x); Double beta = Math.atan2(p3.y - p2.y, p3.x - p2.x); Double angle = (beta - alpha); // Correction de l'angle pour le resituer entre 0 et 2PI while (angle < 0.0 || angle > 2 * Math.PI) { if (angle < 0.0) angle += 2 * Math.PI; else if (angle > 2 * Math.PI) angle -= 2 * Math.PI; } return angle; } public ArrayList<Integer> findNonPairedBaseGroup(Integer get_nearestBase) { // detection 3', 5', bulge ArrayList<Integer> list = new ArrayList<Integer>(); int indice = get_nearestBase; boolean nonpairedUp = true, nonpairedDown = true; while (indice < get_listeBases().size() && nonpairedUp) { if (get_listeBases().get(indice).getElementStructure() == -1) { list.add(indice); indice++; } else { nonpairedUp = false; } } indice = get_nearestBase - 1; while (indice >= 0 && nonpairedDown) { if (get_listeBases().get(indice).getElementStructure() == -1) { list.add(indice); indice--; } else { nonpairedDown = false; } } return list; } public boolean getDrawn() { return _drawn; } public ArrayList<ModeleBP> getStructureAux() { return _structureAux; } public int getIndexFromBaseNumber(int num) { for (int i = 0; i < this._listeBases.size(); i++) { if (_listeBases.get(i).getBaseNumber() == num) { return i; } } return -1; } /** * Adds a base pair to this RNA's structure. Tries to add it to the * secondary structure first, eventually adding it to the 'tertiary' * interactions if it clashes with the current secondary structure. * * @param baseNumber5 * - Base number of the origin of this base pair * @param baseNumber3 * - Base number of the destination of this base pair */ public void addBPToStructureUsingNumbers(int baseNumber5, int baseNumber3) { int i = getIndexFromBaseNumber(baseNumber5); int j = getIndexFromBaseNumber(baseNumber3); addBPToStructure(i, j); } /** * Adds a base pair to this RNA's structure. Tries to add it to the * secondary structure first, possibly adding it to the 'tertiary' * interactions if it clashes with the current secondary structure. * * @param number5 * - Base number of the origin of this base pair * @param number3 * - Base number of the destination of this base pair */ public void addBPToStructureUsingNumbers(int number5, int number3, ModeleBP msbp) { addBPToStructure(getIndexFromBaseNumber(number5), getIndexFromBaseNumber(number3), msbp); } public void addBPToStructure(int index5, int index3) { int i = index5; int j = index3; ModeleBase part5 = _listeBases.get(i); ModeleBase part3 = _listeBases.get(j); ModeleBP msbp = new ModeleBP(part5, part3); addBPToStructure(i, j, msbp); } public void addBPToStructure(int index5, int index3, ModeleBP msbp) { int i = index5; int j = index3; if (j < i) { int k = j; j = i; i = k; } if (i != -1) { if ((_listeBases.get(i).getElementStructure() != -1) || (_listeBases.get(j).getElementStructure() != -1)) { addBPAux(i, j, msbp); return; } for (int k = i + 1; k < j; k++) { ModeleBase tmp = _listeBases.get(k); int l = tmp.getElementStructure(); if (l != -1) { if ((l <= i) || (l >= j)) { addBPAux(i, j, msbp); return; } } } addBP(i, j, msbp); } } public void removeBP(ModeleBP ms) { if (_structureAux.contains(ms)) { _structureAux.remove(ms); } else { ModeleBase m5 = ms.getPartner5(); ModeleBase m3 = ms.getPartner3(); int i = m5.getIndex(); int j = m3.getIndex(); if ((m5.getElementStructure()==m3.getIndex())&&(m3.getElementStructure()==m5.getIndex())) { m5.removeElementStructure(); m3.removeElementStructure(); } } } public void addBP(int i, int j) { if (j < i) { int k = j; j = i; i = k; } ModeleBase part5 = _listeBases.get(i); ModeleBase part3 = _listeBases.get(j); ModeleBP msbp = new ModeleBP(part5, part3); addBP(i, j, msbp); } public void addBP(int i, int j, ModeleBP msbp) { if (j < i) { int k = j; j = i; i = k; } ModeleBase part5 = _listeBases.get(i); ModeleBase part3 = _listeBases.get(j); msbp.setPartner5(part5); msbp.setPartner3(part3); part5.setElementStructure(j, msbp); part3.setElementStructure(i, msbp); } public void addBPAux(int i, int j) { ModeleBase part5 = _listeBases.get(i); ModeleBase part3 = _listeBases.get(j); ModeleBP msbp = new ModeleBP(part5, part3); addBPAux(i, j, msbp); } public void addBPAux(int i, int j, ModeleBP msbp) { if (j < i) { int k = j; j = i; i = k; } ModeleBase part5 = _listeBases.get(i); ModeleBase part3 = _listeBases.get(j); msbp.setPartner5(part5); msbp.setPartner3(part3); _structureAux.add(msbp); } public ArrayList<ModeleBP> getBPsAt(int i) { ArrayList<ModeleBP> result = new ArrayList<ModeleBP>(); if (_listeBases.get(i).getElementStructure() != -1) { result.add(_listeBases.get(i).getStyleBP()); } for (int k = 0; k < _structureAux.size(); k++) { ModeleBP bp = _structureAux.get(k); if ((bp.getPartner5().getIndex() == i) || (bp.getPartner3().getIndex() == i)) { result.add(bp); } } return result; } public ModeleBP getBPStyle(int i, int j) { ModeleBP result = null; if (i > j) { int k = j; j = i; i = k; } if (_listeBases.get(i).getElementStructure() == j) { result = _listeBases.get(i).getStyleBP(); } for (int k = 0; k < _structureAux.size(); k++) { ModeleBP bp = _structureAux.get(k); if ((bp.getPartner5().getIndex() == i) && (bp.getPartner3().getIndex() == j)) { result = bp; } } return result; } public ArrayList<ModeleBP> getSecStrBPs() { ArrayList<ModeleBP> result = new ArrayList<ModeleBP>(); for (int i=0;i<this.getSize();i++) { ModeleBase mb = _listeBases.get(i); int k = mb.getElementStructure(); if ((k!=-1) && (k>i)) { result.add(mb.getStyleBP()) ; } } return result; } public ArrayList<ModeleBP> getAuxBPs() { ArrayList<ModeleBP> result = new ArrayList<ModeleBP>(); for (ModeleBP bp : _structureAux) { result.add(bp); } return result; } public ArrayList<ModeleBP> getAllBPs() { ArrayList<ModeleBP> result = new ArrayList<ModeleBP>(); result.addAll(getSecStrBPs()); result.addAll(getAuxBPs()); return result; } public ArrayList<ModeleBP> getAuxBPs(int i) { ArrayList<ModeleBP> result = new ArrayList<ModeleBP>(); for (ModeleBP bp : _structureAux) { if ((bp.getPartner5().getIndex() == i) || (bp.getPartner3().getIndex() == i)) { result.add(bp); } } return result; } public void setBaseInnerColor(Color c) { for (int i = 0; i < _listeBases.size(); i++) { ModeleBase mb = _listeBases.get(i); mb.getStyleBase().set_base_inner_color(c); } } public void setBaseNumbersColor(Color c) { for (int i = 0; i < _listeBases.size(); i++) { ModeleBase mb = _listeBases.get(i); mb.getStyleBase().set_base_number_color(c); } } public void setBaseNameColor(Color c) { for (int i = 0; i < _listeBases.size(); i++) { ModeleBase mb = _listeBases.get(i); mb.getStyleBase().set_base_name_color(c); } } public void setBaseOutlineColor(Color c) { for (int i = 0; i < _listeBases.size(); i++) { ModeleBase mb = _listeBases.get(i); mb.getStyleBase().set_base_outline_color(c); } } public String getName() { return _name; } public void setName(String n) { _name = n; } public ArrayList<TextAnnotation> getAnnotations() { return _listeAnnotations; } public boolean removeAnnotation(TextAnnotation t) { return _listeAnnotations.remove(t); } public void addAnnotation(TextAnnotation t) { _listeAnnotations.add(t); } public void removeAnnotation(String filter) { ArrayList<TextAnnotation> condamne = new ArrayList<TextAnnotation>(); for (TextAnnotation t : _listeAnnotations) { if (t.getTexte().contains(filter)) { condamne.add(t); } } for (TextAnnotation t : condamne) { _listeAnnotations.remove(t); } } public void clearAnnotations() { _listeAnnotations.clear(); } private boolean _strandEndsAnnotated = false; public void autoAnnotateStrandEnds() { if (! _strandEndsAnnotated) { int tailleListBases=_listeBases.size(); boolean endAnnotate =false; addAnnotation(new TextAnnotation("5'", _listeBases.get(0))); for(int i=0; i<_listeBases.size()-1; i++){ int realposA = _listeBases.get(i).getBaseNumber(); int realposB = _listeBases.get(i+1).getBaseNumber(); if(realposB-realposA!=1){ addAnnotation(new TextAnnotation("3'", _listeBases.get(i))); addAnnotation(new TextAnnotation("5'", _listeBases.get(i+1))); if(i+1==_listeBases.size()-1){ endAnnotate=true; } } } if(!endAnnotate){ addAnnotation(new TextAnnotation("3'", _listeBases.get(tailleListBases-1))); } _strandEndsAnnotated = true; } else { removeAnnotation("3'"); removeAnnotation("5'"); _strandEndsAnnotated = false; } } public void autoAnnotateHelices() { Stack<Integer> p = new Stack<Integer>(); p.push(0); int nbH = 1; while(!p.empty()) { int i = p.pop(); if (i<_listeBases.size()) { ModeleBase mb = _listeBases.get(i); int j = mb.getElementStructure(); if (j==-1) { p.push(i+1); } else { if (j>i) { ModeleBase mbp = _listeBases.get(j); p.push(j+1); ArrayList<ModeleBase> h = new ArrayList<ModeleBase>(); int k = 1; while(mb.getElementStructure()==mbp.getIndex()) { h.add(mb); h.add(mbp); mb = _listeBases.get(i+k); mbp = _listeBases.get(j-k); k++; } try { addAnnotation(new TextAnnotation("H"+nbH++, h , TextAnnotation.HELIX)); } catch (Exception e) { e.printStackTrace(); } p.push(i+k); } } } } } public void autoAnnotateTerminalLoops() { Stack<Integer> p = new Stack<Integer>(); p.push(0); int nbT = 1; while(!p.empty()) { int i = p.pop(); if (i<_listeBases.size()) { ModeleBase mb = _listeBases.get(i); int j = mb.getElementStructure(); if (j==-1) { int k = 1; ArrayList<ModeleBase> t = new ArrayList<ModeleBase>(); while ((i+k<getSize())&&(mb.getElementStructure()==-1)) { t.add(mb); mb = _listeBases.get(i+k); k++; } if (mb.getElementStructure()!=-1) { if (mb.getElementStructure()==i-1) { try { t.add(_listeBases.get(i-1)); t.add(_listeBases.get(i+k-1)); addAnnotation(new TextAnnotation("T"+nbT++, t , TextAnnotation.LOOP)); } catch (Exception e) { e.printStackTrace(); } } p.push(i+k-1); } } else { if (j>i) { p.push(j+1); p.push(i+1); } } } } } public void autoAnnotateInteriorLoops() { Stack<Integer> p = new Stack<Integer>(); p.push(0); int nbT = 1; while(!p.empty()) { int i = p.pop(); if (i<_listeBases.size()) { ModeleBase mb = _listeBases.get(i); int j = mb.getElementStructure(); if (j==-1) { int k = i+1; ArrayList<ModeleBase> t = new ArrayList<ModeleBase>(); boolean terminal = true; while ((k<getSize())&&((mb.getElementStructure()>=i)||(mb.getElementStructure()==-1))) { t.add(mb); mb = _listeBases.get(k); if ((mb.getElementStructure()==-1)||(mb.getElementStructure()<k)) k++; else { p.push(k); terminal = false; k = mb.getElementStructure(); } } if (mb.getElementStructure()!=-1) { if ((mb.getElementStructure()==i-1)&& !terminal) { try { t.add(_listeBases.get(i-1)); t.add(_listeBases.get(k-1)); addAnnotation(new TextAnnotation("I"+nbT++, t , TextAnnotation.LOOP)); } catch (Exception e) { e.printStackTrace(); } p.push(k-1); } } } else { if (j>i) { p.push(i+1); } } } } } @SuppressWarnings("unchecked") public TextAnnotation getAnnotation(int type, ModeleBase base) { TextAnnotation result = null; for(TextAnnotation t : _listeAnnotations) { if (t.getType()==type) { switch(type) { case(TextAnnotation.BASE): if (base == (ModeleBase) t.getAncrage()) return t; break; case(TextAnnotation.HELIX): case(TextAnnotation.LOOP): { ArrayList<ModeleBase> mbl = (ArrayList<ModeleBase>) t.getAncrage(); if (mbl.contains(base)) return t; } break; } } } return result; } private ArrayList<ChemProbAnnotation> _ChemProbAnnotations = new ArrayList<ChemProbAnnotation>(); public void addChemProbAnnotation(ChemProbAnnotation cpa) { _ChemProbAnnotations.add(cpa); } public ArrayList<ChemProbAnnotation> getChemProbAnnotations() { return _ChemProbAnnotations; } public void setColorMapValues(Double[] values, ModeleColorMap cm) { setColorMapValues(values, cm, false); } public void adaptColorMapToValues(ModeleColorMap cm) { double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (int i=0;i<Math.min(_listeBases.size(),_listeBases.size());i++) { ModeleBase mb = _listeBases.get(i); max = Math.max(max,mb.getValue()); min = Math.min(min,mb.getValue()); } cm.rescale(min, max); } public void readValues(Reader r, ModeleColorMap cm) { try { StreamTokenizer st = new StreamTokenizer(r); st.eolIsSignificant(true); ArrayList<ArrayList<Double>> vals = new ArrayList<ArrayList<Double>>(); ArrayList<Double> curVals = new ArrayList<Double>(); int type = st.nextToken(); while (type != StreamTokenizer.TT_EOF) { switch(type) { case (StreamTokenizer.TT_NUMBER): curVals.add(st.nval); break; case (StreamTokenizer.TT_EOL): if (curVals.size()>0) { vals.add(curVals); curVals = new ArrayList<Double>(); } break; } type = st.nextToken(); } if (curVals.size()>0) vals.add(curVals); Double[] v = new Double[vals.size()]; for (int i=0;i<Math.min(vals.size(),getSize());i++) { ArrayList<Double> tab = vals.get(i); v[i] = tab.get(tab.size()-1); } setColorMapValues(v, cm, true); } catch (IOException e) { e.printStackTrace(); } } public void setColorMapValues(Double[] values, ModeleColorMap cm, boolean rescaleColorMap) { if (values.length>0) { for (int i=0;i<Math.min(values.length,_listeBases.size());i++) { ModeleBase mb = _listeBases.get(i); mb.setValue(values[i]); } if (rescaleColorMap){ adaptColorMapToValues(cm); } } } public Double[] getColorMapValues() { Double[] values = new Double[_listeBases.size()]; for (int i=0;i<_listeBases.size();i++) { values[i] = _listeBases.get(i).getValue(); } return values; } public void rescaleColorMap(ModeleColorMap cm) { Double max = Double.MIN_VALUE; Double min = Double.MAX_VALUE; for (int i=0;i<_listeBases.size();i++) { Double value = _listeBases.get(i).getValue(); max = Math.max(max, value); min = Math.min(min, value); } cm.rescale(min, max); } public void setSequence(String s) { setSequence(RNA.explodeSequence(s)); } public void setSequence(List<String> s) { int i = 0; int j = 0; while ((i<s.size())&&(j<_listeBases.size())) { ModeleBase mb = _listeBases.get(j); if (mb instanceof ModeleBaseNucleotide) { ((ModeleBaseNucleotide)mb).set_c(s.get(i)); i++; j++; } else if (mb instanceof ModeleBasesComparison) { ((ModeleBasesComparison)mb).set_base1(((s.get(i).length()>0)?s.get(i).charAt(0):' ')); ((ModeleBasesComparison)mb).set_base2(((s.get(i+1).length()>0)?s.get(i+1).charAt(0):' ')); i+=2;j++; } else j++; } } public void eraseSequence() { int j = 0; while ((j<_listeBases.size())) { ModeleBase mb = _listeBases.get(j); if (mb instanceof ModeleBaseNucleotide) { ((ModeleBaseNucleotide)mb).set_c(""); j++; } else if (mb instanceof ModeleBasesComparison) { ((ModeleBasesComparison)mb).set_base1(' '); ((ModeleBasesComparison)mb).set_base2(' '); j++; } else j++; } } public RNA clone () { try { ByteArrayOutputStream out = new ByteArrayOutputStream (); ObjectOutputStream oout = new ObjectOutputStream (out); oout.writeObject (this); ObjectInputStream in = new ObjectInputStream ( new ByteArrayInputStream (out.toByteArray ())); return (RNA)in.readObject (); } catch (Exception e) { throw new RuntimeException ("cannot clone class [" + this.getClass ().getName () + "] via serialization: " + e.toString ()); } } public ModeleBase getBaseAt(int index) { return this._listeBases.get(index); } public ArrayList<ModeleBase> getBasesAt(Collection<? extends Integer> indices) { ArrayList<ModeleBase> mbs = new ArrayList<ModeleBase>(); for (int i: indices) { mbs.add(getBaseAt(i)); } return mbs; } public ArrayList<ModeleBase> getBasesBetween(int from, int to) { ArrayList<ModeleBase> mbs = new ArrayList<ModeleBase>(); int bck = Math.min(from, to); to = Math.max(from, to); from = bck; for (int i=from;i<=to;i++) { mbs.add(getBaseAt(i)); } return mbs; } public void addHighlightRegion(HighlightRegionAnnotation n) { _listeRegionHighlights.add(n); } public void removeHighlightRegion(HighlightRegionAnnotation n) { _listeRegionHighlights.remove(n); } public void removeChemProbAnnotation(ChemProbAnnotation a) { _ChemProbAnnotations.remove(a); } public void addHighlightRegion(int from, int to, Color fill, Color outline, double radius) { _listeRegionHighlights.add(new HighlightRegionAnnotation(getBasesBetween(from, to),fill,outline,radius)); } public void addHighlightRegion(int from, int to) { _listeRegionHighlights.add(new HighlightRegionAnnotation(getBasesBetween(from, to))); } public ArrayList<HighlightRegionAnnotation> getHighlightRegion() { return _listeRegionHighlights; } /** * Rotates the RNA coordinates by a certain angle * * @param angleDegres * Rotation angle, in degrees */ public void globalRotation(Double angleDegres) { if (_listeBases.size() > 0) { // angle en radian Double angle = angleDegres * Math.PI / 180; // initialisation du minimum et dumaximum Double maxX = _listeBases.get(0).getCoords().x; Double maxY = _listeBases.get(0).getCoords().y; Double minX = _listeBases.get(0).getCoords().x; Double minY = _listeBases.get(0).getCoords().y; // mise a jour du minimum et du maximum for (int i = 0; i < _listeBases.size(); i++) { if (_listeBases.get(i).getCoords().getX() < minX) minX = _listeBases.get(i).getCoords().getX(); if (_listeBases.get(i).getCoords().getY() < minY) minY = _listeBases.get(i).getCoords().getY(); if (_listeBases.get(i).getCoords().getX() > maxX) maxX = _listeBases.get(i).getCoords().getX(); if (_listeBases.get(i).getCoords().getX() > maxY) maxY = _listeBases.get(i).getCoords().getY(); } // creation du point central Point2D.Double centre = new Point2D.Double((maxX - minX) / 2, (maxY - minY) / 2); Double x, y; for (int i = 0; i < _listeBases.size(); i++) { // application de la rotation au centre de chaque base // x' = cos(theta)*(x-xc) - sin(theta)*(y-yc) + xc x = Math.cos(angle) * (_listeBases.get(i).getCenter().getX() - centre.x) - Math.sin(angle) * (_listeBases.get(i).getCenter().getY() - centre.y) + centre.x; // y' = sin(theta)*(x-xc) + cos(theta)*(y-yc) + yc y = Math.sin(angle) * (_listeBases.get(i).getCenter().getX() - centre.x) + Math.cos(angle) * (_listeBases.get(i).getCenter().getY() - centre.y) + centre.y; _listeBases.get(i).setCenter( new Point2D.Double(x, y)); // application de la rotation au coordonnees de chaque // base // x' = cos(theta)*(x-xc) - sin(theta)*(y-yc) + xc x = Math.cos(angle) * (_listeBases.get(i).getCoords().getX() - centre.x) - Math.sin(angle) * (_listeBases.get(i).getCoords().getY() - centre.y) + centre.x; // y' = sin(theta)*(x-xc) + cos(theta)*(y-yc) + yc y = Math.sin(angle) * (_listeBases.get(i).getCoords().getX() - centre.x) + Math.cos(angle) * (_listeBases.get(i).getCoords().getY() - centre.y) + centre.y; _listeBases.get(i).setCoords( new Point2D.Double(x, y)); } } } public boolean testDirectionality(int i, int j, int k) { // Which direction are we heading toward? Point2D.Double pi = getCoords(i); Point2D.Double pj = getCoords(j); Point2D.Double pk = getCoords(k); return testDirectionality(pi, pj, pk); } public static boolean testDirectionality(Point2D.Double pi, Point2D.Double pj, Point2D.Double pk) { // Which direction are we heading toward? double test = (pj.x - pi.x) * (pk.y - pj.y) - (pj.y - pi.y) * (pk.x - pj.x); return test < 0.0; } public double getOrientation() { double maxDist = Double.MIN_VALUE; double angle = 0; for(int i=0; i< _listeBases.size();i++) { ModeleBase b1 = _listeBases.get(i); for(int j=i+1; j< _listeBases.size();j++) { ModeleBase b2 = _listeBases.get(j); Point2D.Double p1 = b1._coords.toPoint2D(); Point2D.Double p2 = b2._coords.toPoint2D(); double dist = p1.distance(p2); if(dist>maxDist) { maxDist = dist; angle = VueUI.computeAngle(p1, p2); } } } return angle; } public boolean hasVirtualLoops() { boolean consecutiveBPs = false; for(int i=0;i<_listeBases.size();i++) { int j =_listeBases.get(i).getElementStructure(); if (j==i+1) { consecutiveBPs = true; } } return ((_drawMode!=DRAW_MODE_LINEAR) &&(_drawMode!=DRAW_MODE_CIRCULAR) &&(consecutiveBPs)); } }