/*
* Author: tdanford
* Date: Sep 24, 2008
*/
package org.seqcode.viz.genomicplot;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.QuadCurve2D;
import org.seqcode.deepseq.StrandedBaseCount;
import org.seqcode.deepseq.StrandedPair;
import org.seqcode.deepseq.experiments.Sample;
import org.seqcode.genome.location.Point;
import org.seqcode.genome.location.Region;
import org.seqcode.genome.location.ScoredRegion;
import org.seqcode.genome.location.ScoredStrandedRegion;
import org.seqcode.gsebricks.verbs.*;
import org.seqcode.gseutils.Closeable;
import org.seqcode.gseutils.Pair;
import org.seqcode.viz.colors.Coloring;
import org.seqcode.viz.paintable.*;
public class ThinOverlapPaintable extends AbstractPaintable {
private boolean axis =true;
private boolean filledColumns=false;
private Region region;
private Vector<Region> highlighted;
private List<Pair<Point,Point>> inters = new ArrayList<Pair<Point, Point>>();
private Color bgColor, highlightColor, loopColor, interColor;
private int bgThick, highlightThick;
private boolean reverse;
private int maxOverlap;
private boolean drawPairedCurves=false;
private Sample sample;
private int threePrimeExt;
private RunningOverlapSum overlaps;
private List<ScoredRegion> pairedRegs=null;
private int[] dataProfile; // y pixel values of where the data line is drawn.
private boolean[][] bgPoints;
private boolean[][] hlPoints;
public ThinOverlapPaintable(Region basereg, Collection<Region> hls, Sample s, int threePrimeExt) {
this(basereg, hls, s, threePrimeExt, false);
}
public ThinOverlapPaintable(Region basereg,Collection<Region> hls, Sample s, int threePrimeExt, boolean drawPairs) {
this(basereg, hls, s, threePrimeExt, drawPairs, null);
}
public ThinOverlapPaintable(Region basereg, Collection<Region> hls, Sample s, int threePrimeExt, boolean drawPairs, List<Pair<Point,Point>> inters) {
region = basereg;
this.inters = inters;
sample = s;
this.threePrimeExt = threePrimeExt;
drawPairedCurves=drawPairs;
if(hls != null && hls.size()>0)
highlighted = new Vector<Region>(hls);
else
highlighted = new Vector<Region>();
bgColor = Coloring.clearer(Color.gray);
highlightColor = Coloring.clearer(Color.red);
maxOverlap = 1;
bgThick = 1;
highlightThick = 2;
overlaps = new RunningOverlapSum(region.getGenome(), region.getChrom());
reverse = false;
loadData();
}
public void setBgColor(Color c){bgColor = c;}
public void setLoopColor(Color c){loopColor = c;}
public void setInterColor(Color c){interColor = c;}
public void setHighlightColor(Color c){highlightColor = c;}
public void setBgThick(int t){bgThick=t;}
public void setHighlightThick(int t){highlightThick=t;}
public void setFilledColumns(boolean b){filledColumns=b;}
public void setReverse(boolean r) {
reverse = r;
dispatchChangedEvent();
}
public void loadData() {
System.out.println("Loading data...");
//Load hits in region, then convert to ScoredStrandedRegions
List<StrandedBaseCount> hits = sample.getBases(region);
float count = 0;
for(StrandedBaseCount sbc : hits){
ScoredStrandedRegion hit = sbc.expandToScoredStrandedRegion(region.getGenome(), region.getChrom(), 0, threePrimeExt);
overlaps.addRegion(hit, (float)hit.getScore());
count += hit.getScore();
}
System.out.println(String.format("\tLoaded %.2f hits.", count));
maxOverlap = overlaps.getMaxOverlap();
System.out.println("Max: "+maxOverlap);
//Load pairs, convert to ScoredRegions
if(drawPairedCurves){
List<StrandedPair> pairs = sample.getPairs(region);
if(pairs!=null){
pairedRegs = new ArrayList<ScoredRegion>();
for(StrandedPair sp : pairs){
ScoredRegion pair = sp.toContiguousRegion();
if(pair !=null)
pairedRegs.add(pair);
}
}
}
}
public void setMaxOverlap(int m){maxOverlap=m;}
public void synchronizeMaxOverlaps(ThinOverlapPaintable p) {
int maxo = Math.max(maxOverlap, p.maxOverlap);
maxOverlap = maxo;
p.maxOverlap = maxo;
dispatchChangedEvent();
p.dispatchChangedEvent();
}
public void paintItem(Graphics g, int x1, int y1, int x2, int y2) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = x2-x1;
dataProfile = new int[w+2];
g2.setColor(Color.white);
g2.fillRect(x1, y1, w, y2-y1);
int[][] changes = overlaps.getChangePoints();
//Split the area into two sections: reads & pairs. The latter is usually zero area.
int readh = drawPairedCurves ? (int)((y2-y1)*0.7) : (y2-y1);
int ready2 = y1+readh;
int pairh = (y2-y1)-readh;
int pairy1 = ready2;
bgPoints = new boolean [w+1][readh+1];
hlPoints = new boolean [w+1][readh+1];
for(int a=0; a<=w; a++)
for(int b=0; b<=readh; b++){
bgPoints[a][b]=false;
hlPoints[a][b]=false;
}
int runningOverlap = 0;
int px = -1, py = -1, pbp = -1;
//Map out the points that must be drawn
for(int i = 0; i < changes.length; i++) {
int bp = changes[i][0];
runningOverlap += changes[i][1];
int x = Math.min(Math.max(getX(bp, w), 0), w);
double yf = Math.min((double)runningOverlap / (double)maxOverlap, 1.0);
int y = (int)Math.round(yf * (double)readh);
if(px == -1) {
if(reverse)
px=w;
else
px = 0;
py = 0; pbp = bp;
}
fillPoints(px, py, x, py, isHighlighted(pbp));
fillPoints(x, py, x, y, isHighlighted(bp));
for(int k = px; k < x; k++)
setDataProfile(k, py);
setDataProfile(x, y);
if(i == changes.length-1) { // last line
fillPoints(x, y, w, y, isHighlighted(bp));
for(int k = x; k < x2; k++)
setDataProfile(k, y);
}
px = x; py = y; pbp = bp;
}
//Fill in the circles...
for(int a=0; a<=w; a++){
int diam, rad;
int x=a+x1;
if(filledColumns){
//Columns
int maxY=-1;
for(int b=0; b<=readh; b++){
if(hlPoints[a][b] || bgPoints[a][b])
maxY=b;
}
if(maxY!=-1){
int y = ready2-maxY;
if(hlPoints[a][maxY]){
diam = highlightThick;
rad = Math.max(1, diam/2);
g2.setColor(highlightColor);
g2.fillRect(x-rad, y-rad, diam, maxY);
}else if(bgPoints[a][maxY]){
diam = bgThick;
rad = Math.max(1, diam/2);
g2.setColor(bgColor);
g2.fillRect(x-rad, y-rad, diam, maxY);
}
}
}else{
//Dots
for(int b=0; b<readh; b++){
int y = ready2-b;
if(hlPoints[a][b]){
diam = highlightThick;
rad = Math.max(1, diam/2);
g2.setColor(highlightColor);
g2.fillOval(x-rad, y-rad, diam, diam);
}else if(bgPoints[a][b]){
diam = bgThick;
rad = Math.max(1, diam/2);
g2.setColor(bgColor);
g2.fillOval(x-rad, y-rad, diam, diam);
}
}
}
}
//Paired-end curves
if(drawPairedCurves){
for(ScoredRegion pair : pairedRegs){
if(region.contains(pair)){
int xA = x1+Math.min(Math.max(getX(pair.getStart(), w), 0), w);
int xB = x1+Math.min(Math.max(getX(pair.getEnd(), w), 0), w);
int xMid = (xA+xB)/2;
int yMid = (int)(((double)pair.getWidth()/(double)region.getWidth()) * pairh*2) + pairy1;
g2.setColor(loopColor);
g2.setStroke(new BasicStroke(1.0f));
QuadCurve2D loop = new QuadCurve2D.Float(xA, pairy1, xMid, yMid, xB, pairy1);
g2.draw(loop);
}
}
for(Pair<Point,Point> inter : inters){
if(region.contains(inter.car()) && region.contains(inter.cdr())){
int xA = x1+Math.min(Math.max(getX(inter.car().getLocation(), w), 0), w);
int xB = x1+Math.min(Math.max(getX(inter.cdr().getLocation(), w), 0), w);
int xMid = (xA+xB)/2;
int yMid = (int)(((double)(inter.cdr().getLocation()-inter.car().getLocation())/(double)region.getWidth()) * pairh*2) + pairy1;
g2.setColor(interColor);
g2.setStroke(new BasicStroke(1.0f));
QuadCurve2D loop = new QuadCurve2D.Float(xA, pairy1, xMid, yMid, xB, pairy1);
g2.draw(loop);
}
}
}
if(axis){
g2.setColor(Color.black);
g2.setStroke(new BasicStroke(1.0f));
g2.drawLine(x1, y1, x1,ready2);
g2.setFont(new Font("Ariel", Font.PLAIN, 16));
FontMetrics metrics = g2.getFontMetrics();
g2.drawString(String.format("%d",maxOverlap), x1+1, y1+(metrics.getHeight()/2));
}
}
public int[] getDataProfile() { return dataProfile; }
private void setDataProfile(int x, int y) {
if(x >= 0 && x < dataProfile.length) {
dataProfile[x] = Math.max(dataProfile[x], y);
} else {
//System.err.println(String.format("%d not in %d profile", x, dataProfile.length));
}
}
private boolean isHighlighted(int bp) {
for(Region r : highlighted) {
if(r.getStart() <= bp && r.getEnd() >= bp) {
return true;
}
}
return false;
}
private int getX(int bp, int pixwidth) {
double f = (double)(bp-region.getStart()) / (double)region.getWidth();
if(reverse) {
f = 1.0 - f;
}
return (int)Math.round(f * (double)pixwidth);
}
private void fillPoints(int p1x, int p1y, int p2x, int p2y, boolean highlighted){
int dx = Math.abs(p2x-p1x), dy = Math.abs(p2y-p1y);
int divs = Math.max(dx, dy);
for(int i = 0; i <= divs; i++) {
double f = (double)i / (double)divs;
double nf = 1.0-f;
int x = (int)Math.round(f*(double)p1x + nf*(double)p2x);
int y = (int)Math.round(f*(double)p1y + nf*(double)p2y);
//System.out.println(x+"\t"+y);
if(highlighted)
hlPoints[x][y]=true;
else
bgPoints[x][y]=true;
}
}
private void strokeLine(Graphics2D g2,
java.awt.Point p1, java.awt.Point p2,
int thick1, int thick2,
Color c1, Color c2) {
int dx = Math.abs(p2.x-p1.x), dy = Math.abs(p2.y-p1.y);
int divs = Math.max(dx, dy);
int[] c1array = Coloring.rgba(c1);
int[] c2array = Coloring.rgba(c2);
int[] carray = new int[4];
for(int i = 0; i <= divs; i++) {
double f = (double)i / (double)divs;
double nf = 1.0-f;
int x = (int)Math.round(f*(double)p1.x + nf*(double)p2.x);
int y = (int)Math.round(f*(double)p1.y + nf*(double)p2.y);
int diam = (int)Math.round(f*(double)thick1 + nf*(double)thick2);
int rad = Math.max(1, diam/2);
for(int j =0; j < carray.length; j++) {
carray[j] = (int)Math.round(f*(double)c1array[j] + nf*(double)c2array[j]);
}
Color c = Coloring.asColor(carray);
g2.setColor(c);
g2.fillOval(x-rad, y-rad, diam, diam);
}
}
/**
* A one-shot (i.e., one use) expander, that automatically closes its inner expander
* when it's been called the first time. Only used in the constructor, above.
*
* @author tdanford
*
* @param <R>
*/
private static class ClosingRegionExpanderWrapper<R extends Region>
implements Expander<Region,Region> {
private Expander<Region,R> exp;
private Mapper<R,R> mapper;
public ClosingRegionExpanderWrapper(Expander<Region,R> e) {
exp = e;
mapper = null;
}
public ClosingRegionExpanderWrapper(Expander<Region,R> e, Mapper<R,R> m) {
exp = e;
mapper = m;
}
public Iterator<Region> execute(Region a) {
Iterator<R> itr = exp.execute(a);
if(mapper != null) {
itr = new MapperIterator<R,R>(mapper,itr);
}
Iterator<Region> regs = new MapperIterator<R,Region>(new CastingMapper<R,Region>(), itr);
LinkedList<Region> regList = new LinkedList<Region>();
while(regs.hasNext()) { regList.addLast(regs.next()); }
if(exp instanceof Closeable) {
((Closeable)exp).close();
}
System.out.println(String.format("ClosingRegionExpanderWrapper: %d", regList.size()));
return regList.iterator();
}
}
}