JAL-4090 JAL-1551 source license
[jalview.git] / src / jalview / renderer / ContactGeometry.java
index 4aef1d8..42dde8d 100644 (file)
@@ -1,25 +1,87 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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.
+ *  
+ * Jalview 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 Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.renderer;
 
+import java.util.Arrays;
 import java.util.Iterator;
+import java.util.List;
 
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
+import jalview.datamodel.HiddenColumns;
+import jalview.renderer.ContactGeometry.contactInterval;
 
+/**
+ * encapsulate logic for mapping between positions in a ContactList and their
+ * rendered representation in a given number of pixels.
+ * 
+ * @author jprocter
+ *
+ */
 public class ContactGeometry
 {
+
+  final ContactListI contacts;
+
+  /**
+   * how many pixels per contact (1..many)
+   */
   final int pixels_step;
 
+  /**
+   * how many contacts per pixel (many > 0)
+   */
   final double contacts_per_pixel;
 
+  /**
+   * number of contacts being mapped
+   */
   final int contact_height;
 
+  /**
+   * number of pixels to map contact_height to
+   */
   final int graphHeight;
 
-  public ContactGeometry(ContactListI contacts, int graphHeight)
+  /**
+   * number of contacts for each pixel_step - to last whole contact
+   */
+  final double contacts_step;
+
+  final int lastStep;
+
+  /**
+   * Bean used to map from a range of contacts to a range of pixels
+   * 
+   * @param contacts
+   * @param graphHeight
+   *          Number of pixels to map given range of contacts
+   */
+  public ContactGeometry(final ContactListI contacts, int graphHeight)
   {
+    this.contacts = contacts;
     this.graphHeight = graphHeight;
     contact_height = contacts.getContactHeight();
     // fractional number of contacts covering each pixel
-    contacts_per_pixel = (graphHeight < 1) ? contact_height
+    contacts_per_pixel = (graphHeight <= 1) ? contact_height
             : ((double) contact_height) / ((double) graphHeight);
 
     if (contacts_per_pixel >= 1)
@@ -33,6 +95,9 @@ public class ContactGeometry
       pixels_step = (int) Math
               .ceil(((double) graphHeight) / (double) contact_height);
     }
+    contacts_step = pixels_step * contacts_per_pixel;
+    lastStep = (int) Math.min((double) graphHeight,
+            ((double) graphHeight) / ((double) pixels_step));
   }
 
   public class contactInterval
@@ -54,24 +119,127 @@ public class ContactGeometry
     public final int pStart;
 
     public final int pEnd;
+
+    @Override
+    public boolean equals(Object obj)
+    {
+      if (obj == null || !(obj instanceof contactInterval))
+      {
+        return false;
+      }
+      contactInterval them = (contactInterval) obj;
+      return cStart == them.cStart && cEnd == them.cEnd && pEnd == them.pEnd
+              && pStart == them.pStart;
+    }
+
+    @Override
+    public String toString()
+    {
+      return "Contacts [" + cStart + "," + cEnd + "] : Pixels [" + pStart
+              + "," + pEnd + "]";
+    }
   }
 
   /**
    * 
+   * @param columnSelection
+   * @param ci
+   * @param visibleOnly
+   *          - when true, only test intersection of visible columns given
+   *          matrix range
+   * @return true if the range on the matrix specified by ci intersects with
+   *         selected columns in the ContactListI's reference frame.
+   */
+
+  boolean intersects(contactInterval ci, ColumnSelection columnSelection,
+          HiddenColumns hiddenColumns, boolean visibleOnly)
+  {
+    boolean rowsel = false;
+    final int[] mappedRange = contacts.getMappedPositionsFor(ci.cStart,
+            ci.cEnd);
+    if (mappedRange == null)
+    {
+      return false;
+    }
+    for (int p = 0; p < mappedRange.length && !rowsel; p += 2)
+    {
+      boolean containsHidden = false;
+      if (visibleOnly && hiddenColumns != null
+              && hiddenColumns.hasHiddenColumns())
+      {
+        // TODO: turn into function on hiddenColumns and create test !!
+        Iterator<int[]> viscont = hiddenColumns.getVisContigsIterator(
+                -1 + mappedRange[p], -1 + mappedRange[p + 1], false);
+        containsHidden = !viscont.hasNext();
+        if (!containsHidden)
+        {
+          for (int[] interval = viscont.next(); viscont
+                  .hasNext(); rowsel |= columnSelection
+                          .intersects(interval[p], interval[p + 1]))
+            ;
+        }
+      }
+      else
+      {
+        rowsel = columnSelection.intersects(-1 + mappedRange[p],
+                -1 + mappedRange[p + 1]);
+      }
+    }
+    return rowsel;
+
+  }
+
+  /**
+   * Return mapped cell intersecting pStart \
+   * 
+   * FIXME: REDUNDANT METHOD - COULD DELETE FIXME: OR RE-IMPLEMENT AS EFFICIENT
+   * RANGE QUERY
+   * 
    * @param pStart
+   *          [0..)
    * @param pEnd
-   * @return range for
+   * @return nearest full cell containing pStart - does not set
+   *         contactInterval.pEnd or cEnd to equivalent position on pEnd !
    */
   public contactInterval mapFor(int pStart, int pEnd)
   {
-    int cStart = (int) Math.floor(pStart * contacts_per_pixel);
-    contactInterval ci = new contactInterval(cStart,
-            (int) Math.min(contact_height,
-                    Math.ceil(
-                            cStart + (pEnd - pStart) * contacts_per_pixel)),
-            pStart, pEnd);
-
-    return ci;
+    if (pStart < 0)
+    {
+      pStart = 0;
+    }
+    if (pEnd < pStart)
+    {
+      pEnd = pStart;
+    }
+    if (pEnd >= graphHeight)
+    {
+      pEnd = graphHeight - 1;
+    }
+    if (pStart >= graphHeight)
+    {
+      pStart = graphHeight - pixels_step;
+    }
+    int step = Math.floorDiv(pStart, pixels_step);
+    return findStep(step);
+  }
+
+  /**
+   * 
+   * @param step
+   *          [0..) n steps covering height and contactHeight
+   * @return contactInterval for step, or null if out of bounds
+   */
+  contactInterval findStep(int step)
+  {
+    if (step < 0 || step > lastStep)
+    {
+      return null;
+    }
+    return new contactInterval((int) Math.floor(contacts_step * step),
+            -1 + (int) Math.min(contact_height,
+                    Math.floor(contacts_step * (step + 1))),
+            pixels_step * step,
+            Math.min(graphHeight, (step + 1) * pixels_step) - 1);
   }
 
   /**
@@ -82,15 +250,31 @@ public class ContactGeometry
    */
   public contactInterval mapFor(int pCentre)
   {
-    int pStart = Math.max(pCentre - pixels_step, 0);
-    int pEnd = Math.min(pStart + pixels_step, graphHeight);
-    int cStart = (int) Math.floor(pStart * contacts_per_pixel);
-    contactInterval ci = new contactInterval(cStart,
-            (int) Math.min(contact_height,
-                    Math.ceil(cStart + (pixels_step) * contacts_per_pixel)),
-            pStart, pEnd);
-
-    return ci;
+    if (pCentre >= graphHeight + pixels_step)
+    {
+      return null;
+    }
+    int step = Math.floorDiv(pCentre, pixels_step);
+    return findStep(step);
+  }
+
+  public List<contactInterval> allSteps()
+  {
+    contactInterval[] array = new contactInterval[lastStep + 1];
+    int csum = 0, psum = 0;
+    for (int i = 0; i <= lastStep; i++)
+    {
+      array[i] = findStep(i);
+      csum += 1 + array[i].cEnd - array[i].cStart;
+      psum += 1 + array[i].pEnd - array[i].pStart;
+    }
+    if (csum != contact_height || psum != graphHeight)
+    {
+      System.err.println("csum = " + csum + " not " + contact_height + "\n"
+              + "psum = " + psum + " not " + graphHeight);
+      return null;
+    }
+    return Arrays.asList(array);
   }
 
   public Iterator<contactInterval> iterateOverContactIntervals(