+/**
+ * File written by Raphael Champeimont
+ * UMR 7238 Genomique des Microorganismes
+ */
+package fr.orsay.lri.varna.models.rna;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
+import fr.orsay.lri.varna.models.VARNAConfig;
+import fr.orsay.lri.varna.models.geom.ComputeArcCenter;
+import fr.orsay.lri.varna.models.geom.ComputeEllipseAxis;
+import fr.orsay.lri.varna.models.geom.CubicBezierCurve;
+import fr.orsay.lri.varna.models.geom.HalfEllipse;
+import fr.orsay.lri.varna.models.geom.LinesIntersect;
+import fr.orsay.lri.varna.models.geom.MiscGeom;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateCurveMethod;
+import fr.orsay.lri.varna.models.templates.DrawRNATemplateMethod;
+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.Tree;
+
+public class DrawRNATemplate {
+ private RNA rna;
+ private RNATemplateMapping mapping;
+ private ArrayList<ModeleBase> _listeBases;
+
+
+ public DrawRNATemplate(RNA rna) {
+ this.rna = rna;
+ this._listeBases = rna.getListeBases();
+ }
+
+ public RNATemplateMapping getMapping() {
+ return mapping;
+ }
+
+
+
+ /**
+ * 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.
+ * @param straightBulges
+ */
+ public void drawRNATemplate(
+ RNATemplate template,
+ VARNAConfig conf,
+ DrawRNATemplateMethod helixLengthAdjustmentMethod,
+ DrawRNATemplateCurveMethod curveMethod, boolean straightBulges)
+ throws RNATemplateDrawingAlgorithmException {
+
+ // 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();
+// }
+
+ mapping = RNATemplateAlign.mapRNAWithTemplate(rna, 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, straightBulges);
+ 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, straightBulges);
+
+ } 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()];
+ double[] angles = new 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,angles, globalIncreaseFactor, translateVectors, straightBulges);
+ 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 = MiscGeom.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);
+ rna.drawLoop(b1,
+ b2,
+ loopCenter.x,
+ loopCenter.y,
+ angle,
+ coords,
+ centers,
+ angles,
+ straightBulges);
+ // If the helix is flipped, we need to compute the symmetric
+ // of the whole loop.
+ if (helix.isFlipped()) {
+ symmetric(loopCenter, helixVector, coords, b1, b2);
+ symmetric(loopCenter, helixVector, centers, b1, b2);
+ }
+ } 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, b2, P0, P1, P2, P3, coords, centers, angles, curveMethod, lastMappedHelix.isFlipped(), straightBulges);
+ }
+
+ } 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 {
+ // Put b1 at an ideal distance from b2, using the "flat exterior loop" method
+ double idealLength = computeStraightLineIdealLength(b1, b2);
+ Point2D.Double j = new Point2D.Double();
+ if (howWeGetInCurrentHelix != null) {
+ computeHelixEndPointDirections(howWeGetInCurrentHelix, new Point2D.Double(), j);
+ } else {
+ j.setLocation(1, 0);
+ }
+ coords[b1].setLocation(coords[b2].x + j.x*idealLength, coords[b2].y + j.y*idealLength);
+ }
+ 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, P0, P1, P2, P3, coords, centers, angles, curveMethod, false, straightBulges);
+ }
+
+ 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());
+ coords[b2].x *= globalIncreaseFactor;
+ coords[b2].y *= globalIncreaseFactor;
+ } else {
+ // Put b2 at an ideal distance from b1, using the "flat exterior loop" method
+ double idealLength = computeStraightLineIdealLength(b1, b2);
+ Point2D.Double j = new Point2D.Double();
+ if (howWeGotOutOfLastHelix != null) {
+ computeHelixEndPointDirections(howWeGotOutOfLastHelix, new Point2D.Double(), j);
+ } else {
+ j.setLocation(1, 0);
+ }
+ coords[b2].setLocation(coords[b1].x + j.x*idealLength, coords[b1].y + j.y*idealLength);
+ }
+
+
+ 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(b1, b2, P0, P1, P2, P3, coords, centers, angles, curveMethod, lastMappedHelix != null ? lastMappedHelix.isFlipped() : false, straightBulges);
+
+ }
+
+
+ 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 (LinesIntersect.linesIntersect(lines[i], 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 * conf._spaceBetweenBases,
+ coords[i].y * conf._spaceBetweenBases));
+ _listeBases.get(i).setCenter(
+ new Point2D.Double(centers[i].x * conf._spaceBetweenBases,
+ centers[i].y * conf._spaceBetweenBases));
+ }
+
+ }
+
+
+
+
+
+
+ /**
+ * 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 (actual helix length / helix length in template).
+ * @param straightBulges
+ */
+ private double computeLengthIncreaseFactor(
+ int[] basesInHelixArray, // IN
+ RNATemplateHelix helix, // IN
+ boolean straightBulges
+ ) {
+ double templateLength = computeHelixTemplateLength(helix);
+ double realLength = computeHelixRealLength(basesInHelixArray,straightBulges);
+ return realLength / templateLength;
+ }
+
+ /**
+ * Compute (actual helix vector - helix vector in template).
+ */
+ private Point2D.Double computeLengthIncreaseDelta(
+ int[] basesInHelixArray, // IN
+ RNATemplateHelix helix, // IN
+ boolean straightBulges
+ ) {
+ double templateLength = computeHelixTemplateLength(helix);
+ double realLength = computeHelixRealLength(basesInHelixArray,straightBulges);
+ 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;
+ }
+ }
+ }
+
+
+ /**
+ * Estimate bulge arc length.
+ */
+ private double estimateBulgeArcLength(int firstBase, int lastBase) {
+ if (firstBase + 1 == lastBase)
+ return RNA.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 += RNA.BASE_PAIR_DISTANCE;
+ k = l;
+ } else {
+ len += RNA.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);
+ }
+
+
+ /**
+ * 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, boolean straightBulges) {
+ return drawHelixLikeTemplateHelix(basesInHelixArray, null, null, null, null, 0, null,straightBulges);
+ }
+
+
+ /**
+ * Result type of countUnpairedLine(), see below.
+ */
+ private static class UnpairedLineCounts {
+ public int nBP, nLD, total;
+ }
+ /**
+ * If we are drawing an unpaired region that may contains helices,
+ * and we are drawing it on a line (curve or straight, doesn't matter),
+ * how many intervals should have a base-pair length (start of an helix)
+ * and how many should have a consecutive unpaired bases length?
+ * Returns an array with three elements:
+ * - answer to first question
+ * - answer to second question
+ * - sum of both, ie. total number of intervals
+ * (this is NOT lastBase-firstBase because bases deep in helices do not count)
+ */
+ private UnpairedLineCounts countUnpairedLine(int firstBase, int lastBase) {
+ UnpairedLineCounts counts = new UnpairedLineCounts();
+ int nBP = 0;
+ int 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++;
+ }
+ }
+ }
+ counts.nBP = nBP;
+ counts.nLD = nLD;
+ counts.total = nBP + nLD;
+ return counts;
+ }
+
+
+ /**
+ * 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.
+ * @param straightBulges
+ *
+ */
+ 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[] angles, // OUT
+ double scaleHelixOrigin, // IN
+ Map<RNATemplateHelix, Point2D.Double> translateVectors // IN (optional, ie. may be null)
+, boolean straightBulges
+ ) {
+ 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*RNA.LOOP_DISTANCE, i.y*RNA.LOOP_DISTANCE);
+ // We want o to be the point where the first base (5' end) is
+ o.x = (o.x - j.x * RNA.BASE_PAIR_DISTANCE / 2) * scaleHelixOrigin;
+ o.y = (o.y - j.y * RNA.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*RNA.BASE_PAIR_DISTANCE,
+ o.y + accDelta.y + i.y*delta + j.y*RNA.BASE_PAIR_DISTANCE);
+ pend.setLocation(o.x + accDelta.x + j.x*RNA.BASE_PAIR_DISTANCE,
+ o.y + accDelta.y + j.y*RNA.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 = MiscGeom.angleFromVector(pstart.x - center.x, pstart.y - center.y);
+ while (b < lastBase) {
+ int l = _listeBases.get(b).getElementStructure();
+ if (b < l && l < lastBase) {
+ len += RNA.BASE_PAIR_DISTANCE;
+ b = l;
+ } else {
+ len += RNA.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
+
+ // Distance between paired bases cannot be changed
+ // (imposed by helix width) but distance between other
+ // bases can be adjusted.
+ UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+ double LD = Math.max((delta - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.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 += RNA.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 = MiscGeom.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);
+ rna.drawLoop(b,
+ l,
+ loopCenter.x,
+ loopCenter.y,
+ beta,
+ coords,
+ centers,
+ angles,
+ straightBulges);
+ // 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));
+ symmetric(loopCenter, v, coords, b, l);
+ symmetric(loopCenter, v, centers, b, l);
+ }
+ // 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*RNA.BASE_PAIR_DISTANCE;
+ p2.y = p1.y + j.y*RNA.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*RNA.BASE_PAIR_DISTANCE;
+ p2.y = p1.y + j.y*RNA.BASE_PAIR_DISTANCE;
+ }
+ } else {
+ // First base pair
+ p1.x = o.x;
+ p1.y = o.y;
+ p2.x = p1.x + j.x*RNA.BASE_PAIR_DISTANCE;
+ p2.y = p1.y + j.y*RNA.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);
+ }
+
+
+
+
+ /**
+ * Return ideal length to draw unpaired region from firstBase to lastBase.
+ */
+ private double computeStraightLineIdealLength(int firstBase, int lastBase) {
+ UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+ return RNA.BASE_PAIR_DISTANCE*counts.nBP + RNA.LOOP_DISTANCE*counts.nLD;
+ }
+
+ /**
+ * Like drawOnBezierCurve(), but on a straight line.
+ */
+ private boolean drawOnStraightLine(
+ int firstBase,
+ int lastBase,
+ Point2D.Double P0,
+ Point2D.Double P3,
+ Point2D.Double[] coords,
+ Point2D.Double[] centers,
+ boolean cancelIfNotGood) {
+
+ Point2D.Double vector = new Point2D.Double(P3.x - P0.x, P3.y - P0.y);
+ double vectorNorm = Math.hypot(vector.x, vector.y);
+
+ UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+ double LD = Math.max((vectorNorm - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+
+ if (cancelIfNotGood && LD < RNA.LOOP_DISTANCE*0.75) {
+ // Bases would be drawn "too" near (0.75 is heuristic)
+ return false;
+ }
+
+ double len = 0;
+ {
+ int b = firstBase;
+ while (b <= lastBase) {
+ coords[b].x = P0.x + vector.x*len/vectorNorm;
+ coords[b].y = P0.y + vector.y*len/vectorNorm;
+ int l = _listeBases.get(b).getElementStructure();
+ if (b < l && l < lastBase) {
+ len += RNA.BASE_PAIR_DISTANCE;
+ b = l;
+ } else {
+ len += LD;
+ b++;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+
+
+ /**
+ * 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.
+ * If cancelIfNotGood, draw only if it would look good,
+ * otherwise just return false.
+ */
+ private boolean drawOnBezierCurve(
+ int firstBase,
+ int lastBase,
+ Point2D.Double P0,
+ Point2D.Double P1,
+ Point2D.Double P2,
+ Point2D.Double P3,
+ Point2D.Double[] coords,
+ Point2D.Double[] centers,
+ boolean cancelIfNotGood) {
+
+ UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+
+ // Draw the bases of the sequence along a Bezier curve
+ int n = counts.total;
+
+ // 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 LD = Math.max((curveLength - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+
+ if (cancelIfNotGood && LD < RNA.LOOP_DISTANCE*0.75) {
+ // Bases would be drawn "too" near (0.75 is heuristic)
+ return false;
+ }
+
+ double[] t = new double[n+1];
+
+ {
+ double len = 0;
+ int k = 0;
+ int b = firstBase;
+ while (b <= lastBase) {
+ t[k] = len;
+ k++;
+
+ int l = _listeBases.get(b).getElementStructure();
+ if (b < l && l < lastBase) {
+ len += RNA.BASE_PAIR_DISTANCE;
+ b = l;
+ } else {
+ len += LD;
+ b++;
+ }
+ }
+ }
+
+ Point2D.Double[] sequenceBasesCoords = bezier.uniformParam(t);
+
+ {
+ int k = 0;
+ int b = firstBase;
+ while (b <= lastBase) {
+ coords[b] = sequenceBasesCoords[k];
+ int l = _listeBases.get(b).getElementStructure();
+ if (b < l && l < lastBase) {
+ b = l;
+ } else {
+ b++;
+ }
+ k++;
+ }
+ }
+
+ return true;
+ }
+
+
+
+ private void drawOnEllipse(
+ int firstBase,
+ int lastBase,
+ Point2D.Double P0,
+ Point2D.Double P3,
+ Point2D.Double[] coords,
+ Point2D.Double[] centers,
+ boolean reverse) {
+
+ UnpairedLineCounts counts = countUnpairedLine(firstBase, lastBase);
+ double halfEllipseLength = RNA.BASE_PAIR_DISTANCE*counts.nBP + RNA.LOOP_DISTANCE*counts.nLD;
+ double fullEllipseLength = halfEllipseLength*2;
+
+ double axisA = P0.distance(P3)/2;
+ double axisB = ComputeEllipseAxis.computeEllipseAxis(axisA, fullEllipseLength);
+
+ if (axisB == 0) {
+ // Ellipse is in fact a line, and it is much faster to draw it as such.
+ drawOnStraightLine(firstBase, lastBase, P0, P3, coords, centers, false);
+ return;
+ }
+
+ //System.out.println("Ellipse a=" + axisA + " b=" + axisB);
+
+ int n = counts.total;
+
+ // We choose to approximate the Bezier curve by 10*n straight lines.
+ HalfEllipse curve = new HalfEllipse(axisA, axisB, 10*n);
+ double curveLength = curve.getApproxCurveLength();
+
+
+ double LD = Math.max((curveLength - counts.nBP*RNA.BASE_PAIR_DISTANCE) / (double) counts.nLD, 0);
+
+ double[] t = new double[n+1];
+
+ {
+ double len = 0;
+ int k = 0;
+ int b = firstBase;
+ while (b <= lastBase) {
+ t[k] = len;
+ k++;
+
+ int l = _listeBases.get(b).getElementStructure();
+ if (b < l && l < lastBase) {
+ len += RNA.BASE_PAIR_DISTANCE;
+ b = l;
+ } else {
+ len += LD;
+ b++;
+ }
+ }
+ }
+
+ // Ellipse with axis = X,Y
+ Point2D.Double[] sequenceBasesCoords = curve.uniformParam(t);
+
+ // Compute the symmetric half-ellipse
+ if (reverse) {
+ AffineTransform tranform1 = new AffineTransform();
+ tranform1.scale(1, -1);
+ tranform1.transform(sequenceBasesCoords, 0, sequenceBasesCoords, 0, sequenceBasesCoords.length);
+ }
+
+ // Translate + rotate such that ellipse is at the right place
+ AffineTransform tranform = HalfEllipse.matchAxisA(P0, P3);
+ tranform.transform(sequenceBasesCoords, 0, sequenceBasesCoords, 0, sequenceBasesCoords.length);
+
+ {
+ int k = 0;
+ int b = firstBase;
+ while (b <= lastBase) {
+ coords[b] = sequenceBasesCoords[k];
+ //coords[b] = curve.standardParam((double) k/n);
+ //System.out.println(b + " " + coords[b]);
+ int l = _listeBases.get(b).getElementStructure();
+ if (b < l && l < lastBase) {
+ b = l;
+ } else {
+ b++;
+ }
+ k++;
+ }
+ }
+
+ }
+
+ /**
+ * 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.
+ *
+ * Argument reverse says if we should reverse the direction of inserted helices.
+ */
+ 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,
+ double[] angles,
+ DrawRNATemplateCurveMethod curveMethod,
+ boolean reverse,
+ boolean straightBulges) {
+
+ // 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 (curveMethod == DrawRNATemplateCurveMethod.ALWAYS_REPLACE_BY_ELLIPSES) {
+ drawOnEllipse(firstBase, lastBase, P0, P3, coords, centers, reverse);
+ } else if (curveMethod == DrawRNATemplateCurveMethod.SMART) {
+ boolean passed;
+ if (P1 != null && P2 != null) {
+ passed = drawOnBezierCurve(firstBase, lastBase, P0, P1, P2, P3, coords, centers, true);
+ } else {
+ passed = drawOnStraightLine(firstBase, lastBase, P0, P3, coords, centers, true);
+ }
+ if (!passed) {
+ drawOnEllipse(firstBase, lastBase, P0, P3, coords, centers, reverse);
+ }
+ } else {
+ if (P1 != null && P2 != null) {
+ drawOnBezierCurve(firstBase, lastBase, P0, P1, P2, P3, coords, centers, false);
+ } else {
+ drawOnStraightLine(firstBase, lastBase, P0, P3, coords, centers, false);
+ }
+ }
+ }
+ // 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 = MiscGeom.angleFromVector(b2pos.x - b1pos.x, b2pos.y - b1pos.y);
+ rna.drawLoop(b1,
+ b2,
+ (b1pos.x + b2pos.x)/2,
+ (b1pos.y + b2pos.y)/2,
+ alpha - Math.PI / 2,
+ coords,
+ centers,
+ angles,
+ straightBulges);
+ if (reverse) {
+ Point2D.Double symAxisVect = new Point2D.Double(coords[b2].x - coords[b1].x, coords[b2].y - coords[b1].y);
+ symmetric(coords[b1], symAxisVect, coords, b1, b2);
+ symmetric(coords[b1], symAxisVect, centers, b1, b2);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Compute the symmetric of all the points from first to last 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,
+ int first,
+ int last) {
+ // ||v||^2
+ double lv = v.x*v.x + v.y*v.y;
+ for (int i=first; i<=last; 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
+ boolean straightBulges
+ ) {
+ 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, straightBulges);
+ newDeltaVector = new Point2D.Double(parentDeltaVector.x+helixDeltaVector.x, parentDeltaVector.y+helixDeltaVector.y);
+ }
+ }
+ for (Tree<RNANodeValueTemplate> subtree: tree.getChildren()) {
+ computeHelixTranslations(subtree, translateVectors, mapping, newDeltaVector, straightBulges);
+ }
+ }
+
+ private Map<RNATemplateHelix, Point2D.Double> computeHelixTranslations(
+ Tree<RNANodeValueTemplate> tree, // IN
+ RNATemplateMapping mapping, // IN
+ boolean straightBulges
+ ) {
+ Map<RNATemplateHelix, Point2D.Double> translateVectors = new HashMap<RNATemplateHelix, Point2D.Double>();
+ computeHelixTranslations(tree, translateVectors, mapping, new Point2D.Double(0,0), straightBulges);
+ return translateVectors;
+ }
+}