/*
* 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;
// }
//
// }
}
}