/* * 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 . * 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; /** * 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 : ((double) contact_height) / ((double) graphHeight); if (contacts_per_pixel >= 1) { // many contacts rendered per pixel pixels_step = 1; } else { // pixel height for each contact 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 { public contactInterval(int cStart, int cEnd, int pStart, int pEnd) { this.cStart = cStart; this.cEnd = cEnd; this.pStart = pStart; this.pEnd = pEnd; } // range on contact list public final int cStart; public final int cEnd; // range in pixels 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 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 nearest full cell containing pStart - does not set * contactInterval.pEnd or cEnd to equivalent position on pEnd ! */ public contactInterval mapFor(int pStart, int pEnd) { 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); } /** * return the cell containing given pixel * * @param pCentre * @return range for pCEntre */ public contactInterval mapFor(int pCentre) { if (pCentre >= graphHeight + pixels_step) { return null; } int step = Math.floorDiv(pCentre, pixels_step); return findStep(step); } public List 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 iterateOverContactIntervals( int graphHeight) { // NOT YET IMPLEMENTED return null; // int cstart = 0, cend; // // for (int ht = y2, // eht = y2 - graphHeight; ht >= eht; ht -= pixels_step) // { // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel); // cend = (int) Math.min(contact_height, // Math.ceil(cstart + contacts_per_pixel * pixels_step)); // // return new Iterator() { // // @Override // public boolean hasNext() // { // // TODO Auto-generated method stub // return false; // } // // @Override // public contactIntervals next() // { // // TODO Auto-generated method stub // return null; // } // // } } }