package org.phylowidget.render;
import java.awt.AlphaComposite;
import java.awt.Composite;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.Point2D;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import org.andrewberman.ui.Color;
import org.andrewberman.ui.TextField;
import org.phylowidget.PWContext;
import org.phylowidget.PWPlatform;
import org.phylowidget.PhyloTree;
import org.phylowidget.PhyloWidget;
import org.phylowidget.UsefulConstants;
import org.phylowidget.tree.PhyloNode;
import org.phylowidget.tree.RootedTree;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PGraphicsJava2D;
public final class NodeRenderer implements UsefulConstants
{
static Graphics2D g2;
static FontMetrics fm;
static BasicTreeRenderer r;
static PWContext context = PWPlatform.getInstance().getThisAppContext();
static float[] ZEROES = new float[] { 0, 0 };
/*
* Create the taxon color map, which is used if this tree has NHX annotations.
*/
static HashMap<String, Integer> taxonColorMap = new HashMap<String, Integer>();
static NodeRender nr = new NodeRender();
static LineRender lineRender = new LineRender();
static CigarRender cr = new CigarRender();
static ImageRender ir = new ImageRender();
static LabelRender lr = new LabelRender();
static RenderItem[] renderables = new RenderItem[] { ir, cr, lr };
static RenderItem[] structRenderables = new RenderItem[] { lineRender, nr };
public NodeRenderer()
{
}
public static final void render(BasicTreeRenderer r, PhyloNode n)
{
renderImpl(r, n, true);
}
static final void renderImpl(BasicTreeRenderer r, PhyloNode n, boolean actuallyRender)
{
PGraphics canvas = r.canvas;
if (canvas == null)
{
// throw new RuntimeException("Null canvas!");
return;
}
// g2 = r.canvas.g2;
NodeRenderer.r = r;
// Translate the canvas to the node's x and y coords.
float x = n.getX();
float y = n.getY();
if (r.treeLayout instanceof LayoutCladogram && r.tree.isLeaf(n))
{
if (context.config().alignLabels || n.getAnnotation("cigar") != null)
{
PhyloNode mostDistant = (PhyloNode) r.tree.getFurthestLeafFromVertex(r.tree.getRoot());
x = mostDistant.getX();
}
}
canvas.pushMatrix();
canvas.translate(x, y);
canvas.rotate(n.getAngle());
float dMult = 1;
if (n.getTextAlign() == PhyloNode.ALIGN_RIGHT)
dMult = -1;
n.rect.setFrame(n.getX(), n.getY(), 0, 0);
// GJ 19-09-08: fixed the spacing of rendered elements.
float rowHeight = r.getTextSize();
float dotWidth = r.getNodeOffset(n);
if (context.config().treatNodesAsLabels)
{
boolean drawNode = (actuallyRender && n.drawLabel) || context.config().showAllLeafNodes;
nr.render(canvas, n, drawNode, true);
}
if (r.getTree().isCollapsed(n))
{
canvas.translate(rowHeight * .3f, 0);
}
canvas.translate(dotWidth * dMult + RenderConstants.labelSpacing * dMult * rowHeight, 0);
float dx = 0;
for (RenderItem ri : renderables)
{
boolean drawLabel = (n.drawLabel && actuallyRender);
float[] xy = ri.render(canvas, n, drawLabel, true);
dx = xy[0];
canvas.translate(dx + (RenderConstants.labelSpacing) * dMult * rowHeight, 0);
}
canvas.popMatrix();
for (RenderItem ri : structRenderables)
{
boolean drawNode = (actuallyRender && n.drawLineAndNode);
if (ri == nr)
{
if (context.config().treatNodesAsLabels)
continue;
// GJ 19-09-08 clarify: this is a special case, where we want all nodes to be drawn.
drawNode = (actuallyRender && n.drawLineAndNode) || context.config().showAllLeafNodes;
}
ri.render(canvas, n, drawNode, false);
}
}
public final static void setCornerPoints(BasicTreeRenderer r, PhyloNode n)
{
renderImpl(r, n, false);
}
static void getColorsForSpeciesMap()
{
int n = taxonColorMap.size();
Set<String> keys = taxonColorMap.keySet();
float step = 1f / (n + 1f);
float pos = 0;
for (String key : keys)
{
pos += step;
int color = Color.HSBtoRGB(pos, .7f, .85f);
taxonColorMap.put(key, color);
}
}
static float strokeForNode(PhyloNode n)
{
float stroke = r.baseStroke;
if (n.found)
{
stroke *= RenderConstants.foundStroke;
return stroke;
}
switch (n.getState())
{
case (PhyloNode.CUT):
stroke *= RenderConstants.dimStroke;
break;
case (PhyloNode.COPY):
stroke *= RenderConstants.copyStroke;
break;
case (PhyloNode.NONE):
default:
stroke *= RenderConstants.regStroke;
break;
}
return stroke;
}
static private Point2D.Float tempPoint = new Point2D.Float();
private static final void registerPoint(PGraphics canvas, PhyloNode n, float x, float y)
{
if (n.getLabel().length() == 0)
return;
float screenX = canvas.screenX(x, y);
float screenY = canvas.screenY(x, y);
// if (n.rect.contains(screenX,screenY))
// return;
n.rect.add(screenX, screenY);
n.range.loX = (float) n.rect.getMinX();
n.range.hiX = (float) n.rect.getMaxX();
n.range.loY = (float) n.rect.getMinY();
n.range.hiY = (float) n.rect.getMaxY();
}
private static final float getFloatAnnotation(PhyloNode n, String key)
{
String ann = n.getAnnotation(key);
if (ann == null)
return -1;
try {
float f = Float.parseFloat(ann);
return f;
} catch (Exception e)
{
return -1;
}
}
public static abstract class RenderItem
{
protected float offX;
protected float offY;
public float[] render(PGraphics canvas, PhyloNode n, boolean actuallyRender, boolean preTransformed)
{
if (preTransformed)
{
offX = 0;
offY = 0;
} else
{
offX = n.getX();
offY = n.getY();
}
return null;
}
}
public static class NodeRender extends RenderItem
{
@Override
public float[] render(PGraphics canvas, PhyloNode n, boolean actuallyRender, boolean preTransformed)
{
super.render(canvas, n, actuallyRender, preTransformed);
return drawNodeMarkerImpl(canvas, n, actuallyRender);
}
private float nodeSizeForNode(PhyloNode n)
{
float thisDotSize = r.dotWidth;
// Multiply by inner ratio.
if (!r.tree.isLeaf(n))
{
thisDotSize *= context.config().innerNodeRatio;
}
// Look for the NSZ annotations.
float nS = getFloatAnnotation(n, NODE_SIZE);
if (nS == -1)
nS = getFloatAnnotation(n, NODE_SIZE_ALT);
if (nS > -1)
thisDotSize *= nS;
return thisDotSize;
}
protected float[] drawNodeMarkerImpl(PGraphics canvas, PhyloNode n, boolean actuallyRender)
{
float thisDotSize = nodeSizeForNode(n);
if (canvas == null)
return new float[] { thisDotSize / 2, thisDotSize / 2 };
canvas.fill(nodeColor(n));
canvas.noStroke();
RootedTree tree = r.tree;
if (thisDotSize == 0)
return ZEROES;
if (n.isNHX() && context.config().colorDuplications && !tree.isLeaf(n))
{
String s = n.getAnnotation(DUPLICATION);
if (s != null)
{
if (PhyloNode.parseTruth(s))
{
canvas.fill(RenderConstants.copyColor.getRGB());
} else
{
canvas.fill(new Color(0, 0, 255).getRGB());
}
}
}
// Transparent google chart: http://chart.apis.google.com/chart?cht=p&chd=t:60,40&chs=50x50&chf=bg,s,FFFFFF00
// GJ 19-09-08: Try out registering node points for overlap...
if (context.config().treatNodesAsLabels)
{
registerPoint(canvas, n, offX - thisDotSize / 2, offY - thisDotSize / 2);
registerPoint(canvas, n, offX + thisDotSize / 2, offY + thisDotSize / 2);
}
if (!actuallyRender)
{
return new float[] { thisDotSize / 2, thisDotSize / 2 };
}
int shape = getNodeShape(n);
if (shape == TRIANGLE)
{
canvas.beginShape();
float r = thisDotSize * .75f;
canvas.vertex(offX + 0.866f * r, offY + 0.5f * r);
canvas.vertex(offX - 0.866f * r, offY + 0.5f * r);
canvas.vertex(offX, offY - 1 * r);
canvas.endShape();
} else if (shape == SQUARE)
{
canvas.rect(offX - thisDotSize / 2, offY - thisDotSize / 2, thisDotSize, thisDotSize);
} else if (shape == STAR)
{
canvas.pushMatrix();
canvas.translate(offX,offY);
canvas.beginShape();
float r = thisDotSize ;
float s = thisDotSize * .4f;
for (int i=0; i < 10; i++)
{
double theta = Math.PI*2 * (i/10f) - Math.PI/2;
if (i % 2 == 0)
{
canvas.vertex((float)(r * Math.cos(theta)),(float)(r*Math.sin(theta)));
} else
{
canvas.vertex((float)(s * Math.cos(theta)),(float)(s*Math.sin(theta)));
}
}
canvas.endShape();
canvas.popMatrix();
} else if (shape == FILLED_CIRCLE)
{
// Inside.
canvas.ellipseMode(PGraphics.CENTER);
canvas.ellipse(offX, offY, thisDotSize, thisDotSize);
// Outline.
canvas.ellipseMode(PGraphics.CENTER);
canvas.strokeWeight(thisDotSize/10f);
canvas.stroke(Color.black.getRGB());
canvas.noFill();
canvas.ellipse(offX,offY,thisDotSize,thisDotSize);
} else
{
// Default to circle
canvas.ellipseMode(PGraphics.CENTER);
canvas.ellipse(offX, offY, thisDotSize, thisDotSize);
}
return new float[] { thisDotSize / 2, thisDotSize / 2 };
}
static final int CIRCLE = 0;
static final int SQUARE = 1;
static final int TRIANGLE = 2;
static final int STAR = 3;
static final int FILLED_CIRCLE = 4;
static int getNodeShape(PhyloNode n)
{
String annotation = n.getAnnotation(UsefulConstants.NODE_SHAPE);
if (annotation == null)
annotation = n.getAnnotation(UsefulConstants.NODE_SHAPE_ALT);
String shape = context.config().nodeShape.toLowerCase();
if (annotation != null)
{
shape = annotation.toLowerCase();
}
if (shape.startsWith(SHAPE_TRIANGLE))
return TRIANGLE;
else if (shape.startsWith(SHAPE_SQUARE))
return SQUARE;
else if (shape.startsWith(SHAPE_STAR))
return STAR;
else if (shape.startsWith(SHAPE_FILLED_CIRCLE))
return FILLED_CIRCLE;
else
return CIRCLE;
}
static int nodeColor(PhyloNode n)
{
if (n.found)
{
return RenderConstants.foundColor.getRGB();
}
if (n == ((PhyloTree) r.tree).hoveredNode && context.config().colorHoveredBranch)
{
return RenderConstants.hoverColor.getRGB();
}
switch (n.getState())
{
case (PhyloNode.CUT):
return RenderConstants.dimColor.getRGB();
case (PhyloNode.COPY):
return RenderConstants.copyColor.getRGB();
case (PhyloNode.NONE):
default:
int c = context.config().getNodeColor().getRGB();
String nodeColor = n.getAnnotation(NODE_COLOR);
if (nodeColor == null)
nodeColor = n.getAnnotation(NODE_COLOR_ALT);
if (nodeColor != null)
{
c = Color.parseColor(nodeColor).getRGB();
}
return c;
}
}
}
public static class LineRender extends RenderItem
{
@Override
public float[] render(PGraphics canvas, PhyloNode n, boolean actuallyRender, boolean preTransformed)
{
super.render(canvas, n, actuallyRender, preTransformed);
if (!actuallyRender)
return ZEROES;
PhyloNode parent = (PhyloNode) n.getParent();
if (parent != null)
drawLine(r, parent, n);
return ZEROES;
}
protected void drawLine(BasicTreeRenderer r, PhyloNode p, PhyloNode c)
{
if (p == null)
return;
/*
* Keep in mind that p may be null (in the case of root node).
*/
float weight = nodeStroke(r, c);
// weight = 1;
if (weight == 0)
return;
/*
* Only set a minimum weight of 0.5 if we're not outputting to a file.
*/
if (!RenderOutput.isOutputting)
weight = Math.max(0.5f, weight);
r.canvas.strokeWeight(weight);
r.canvas.stroke(lineColor(c));
PhyloTree tree = (PhyloTree) r.getTree();
if (c == tree.hoveredNode && context.config().colorHoveredBranch)
{
r.canvas.stroke(RenderConstants.hoverColor.getRGB());
r.canvas.strokeWeight(weight * RenderConstants.hoverStroke);
}
if (c.isNHX() && context.config().colorBootstrap && !c.found)
{
// double d = getFloatAnnotation(c, BOOTSTRAP);
double d = -1;
if (d > -1)
{
d = (100 - d) * 200f / 100f;
d = r.clamp(d, 0, 255);
r.canvas.stroke(context.config().getBranchColor().brighter(d).getRGB());
}
}
r.getLayout().drawLine(r.canvas, p, c);
// r.canvas.line(p.getRealX(), p.getRealY(), p.getRealX(), c.getRealY());
// r.canvas.line(p.getRealX(), c.getRealY(), c.getRealX(),c.getRealY());
}
static float nodeStroke(BasicTreeRenderer r, PhyloNode n)
{
float stroke = strokeForNode(n);
float bSize = getFloatAnnotation(n, BRANCH_SIZE);
if (bSize == -1)
bSize = getFloatAnnotation(n, BRANCH_SIZE_ALT);
if (bSize > -1)
stroke *= bSize;
return stroke;
}
static int lineColor(PhyloNode n)
{
if (n.found)
{
return RenderConstants.foundColor.getRGB();
}
switch (n.getState())
{
case (PhyloNode.CUT):
return RenderConstants.dimColor.getRGB();
case (PhyloNode.COPY):
return RenderConstants.copyColor.getRGB();
case (PhyloNode.NONE):
default:
int c = context.config().getBranchColor().getRGB();
String branchColor = n.getAnnotation(BRANCH_COLOR);
if (branchColor == null)
branchColor = n.getAnnotation(BRANCH_COLOR_ALT);
if (branchColor != null)
{
c = Color.parseColor(branchColor).getRGB();
}
return c;
}
}
}
static class ImageRender extends RenderItem
{
static boolean fitImagesToSquare = true;
@Override
public float[] render(PGraphics canvas, PhyloNode n, boolean actuallyRender, boolean preTransformed)
{
super.render(canvas, n, actuallyRender, preTransformed);
return renderImage(r, n, actuallyRender);
}
private float imageSizeForNode(BasicTreeRenderer r, PhyloNode n)
{
float thisRowSize = r.getTextSize() * context.config().imageSize * n.bulgeFactor;
if (!context.config().showAllLabels)
thisRowSize = Math.max(thisRowSize, context.config().minTextSize);
// If we find a NHX image size annotation, scale accordingly.
float iMult = getFloatAnnotation(n, IMAGE_SIZE);
if (iMult == -1)
iMult = getFloatAnnotation(n, IMAGE_SIZE_ALT);
if (iMult > -1)
thisRowSize *= iMult;
return thisRowSize;
}
/*
* Renders a given image and returns the width that it's taking up.
*/
float[] renderImage(BasicTreeRenderer r, PhyloNode n, boolean actuallyRender)
{
String imgS = n.getAnnotation("img");
if (imgS == null)
return ZEROES;
boolean alignRight = false;
if (n.getTextAlign() == PhyloNode.ALIGN_RIGHT)
alignRight = true;
float rowHeight = imageSizeForNode(r, n);
float[] size = imageSize(n, rowHeight);
float scaledW = size[0];
float scaledH = size[1];
if (actuallyRender)
{
Graphics2D g2 = ((PGraphicsJava2D) r.canvas).g2;
PGraphics canvas = r.canvas;
float dx = (rowHeight - scaledW) / 2;
if (alignRight)
dx -= scaledW;
Image img = null;
if (RenderOutput.isOutputting)
{
try
{
img = ImageIO.read(new URL(n.getFullImageURL()));
} catch (Exception e)
{
e.printStackTrace();
img = context.trees().imageLoader.getImageForNode(n);
}
} else
{
float minSide = Math.min(scaledH, scaledW);
if (minSide > 300)
{
// System.out.println("Loading full image...");
n.loadFullImage();
}
img = context.trees().imageLoader.getImageForNode(n);
}
if (img != null)
{
float alpha = 1.0f;
if (n.getAnnotation("img_a") != null)
{
alpha = Float.parseFloat(n.getAnnotation("img_a"));
alpha += 0.05;
if (alpha >= 1)
{
n.clearAnnotation("img_a");
alpha = 1;
} else
{
n.setAnnotation("img_a", alpha);
}
}
Composite old = g2.getComposite();
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha));
g2.drawImage(img, (int) dx, (int) -scaledH / 2, (int) scaledW, (int) scaledH, null);
g2.setComposite(old);
}
if (RenderOutput.isOutputting && img != null)
{
img.flush();
img = null;
}
// canvas.image(img, dx, -scaledH / 2, scaledW, scaledH);
}
if (fitImagesToSquare)
return new float[] { rowHeight, rowHeight };
else
return new float[] { scaledW, rowHeight };
}
public float[] imageSize(PhyloNode n, float rowHeight)
{
Image img = context.trees().imageLoader.getImageForNode(n);
if (img == null)
return new float[] { 0, 0 };
float scaling = 1;
float imgW = img.getWidth(null);
float imgH = img.getHeight(null);
if (fitImagesToSquare)
{
if (imgW > imgH)
{
scaling = rowHeight / imgW;
} else
{
scaling = rowHeight / imgH;
}
} else
{
scaling = rowHeight / imgH;
}
float scaledW = imgW * scaling;
float scaledH = imgH * scaling;
return new float[] { scaledW, scaledH };
}
}
static class LabelRender extends RenderItem
{
@Override
public float[] render(PGraphics canvas, PhyloNode n, boolean actuallyRender, boolean preTransformed)
{
super.render(canvas, n, actuallyRender, preTransformed);
// Calculate the row / text size.
float curTextSize = textSizeForNode(r, n);
boolean alignRight = false;
if (n.getTextAlign() == PhyloNode.ALIGN_RIGHT)
alignRight = true;
// Grab the NHX annotated label size mult. factor
float labelMult = getFloatAnnotation(n, LABEL_SIZE);
if (labelMult == -1)
labelMult = getFloatAnnotation(n, LABEL_SIZE_ALT);
if (labelMult > -1)
curTextSize *= labelMult;
// If collapsed, label size is smaller.
if (r.getTree().isCollapsed(n))
{
curTextSize *= .6f;
}
boolean alwaysRender = false;
float always = getFloatAnnotation(n, LABEL_ALWAYSSHOW);
if (always > -1)
alwaysRender = true;
always = getFloatAnnotation(n, LABEL_ALWAYSSHOW_ALT);
if (always > -1)
alwaysRender = true;
/*
* Early exit strategy if text is too small. Don't do this if we're outputting to a file.
*/
if (curTextSize < .5f && !RenderOutput.isOutputting && !alwaysRender)
{
return ZEROES;
}
if (context.config().textRotation != 0)
{
canvas.rotate(PApplet.radians(context.config().textRotation));
}
RootedTree tree = r.tree;
if (tree.isLeaf(n) && (n.found || alwaysRender))
{
/*
* Draw a background rect.
*/
if (actuallyRender)
{
canvas.noStroke();
canvas.fill(RenderConstants.foundBackground.getRGB());
if (alignRight)
canvas.rect(-n.unitTextWidth * curTextSize, -curTextSize / 2,
(float) (n.unitTextWidth * curTextSize), curTextSize);
else
canvas.rect(0, -curTextSize / 2, (float) (n.unitTextWidth * curTextSize), curTextSize);
}
}
float dx = curTextSize * n.unitTextWidth;
/*
* THIS IS THE MAIN LABEL DRAWING CODE. SO SLEEK, SO SIMPLE!!!
*/
canvas.fill(textColor(n));
curTextSize = Math.min(curTextSize, 128);
// if (curTextSize*100 == 0 && actuallyRender)
// return new float[]{dx,curTextSize};
if (canvas.textFont != null)
canvas.textSize(curTextSize);
if (n.found)
{
canvas.fill(RenderConstants.foundForeground.getRGB());
}
if (!tree.isLeaf(n))
{
if (context.config().showCladeLabels)
{
float s = strokeForNode(n);
// TODO: Make a background rect, like we do for found nodes.
// if (actuallyRender)
// {
// canvas.fill(RenderConstants.foundBackground.getRGB());
// // canvas.noStroke();
// canvas.rect(offX - dx, offY + curTextSize / 2 - curTextSize / 3 - s, offX, offY - curTextSize
// / 2 - curTextSize / 3 - s);
// }
canvas.fill(textColor(n));
curTextSize *= 0.75f;
dx = curTextSize * n.unitTextWidth;
registerPoint(canvas, n, offX, offY - curTextSize / 2 - curTextSize / 3 - s);
registerPoint(canvas, n, offX - dx, offY + curTextSize / 2 - curTextSize / 3 - s);
if (actuallyRender)
{
canvas.textSize(curTextSize);
canvas.textAlign(canvas.RIGHT, canvas.BASELINE);
canvas.fill(textColor(n));
// canvas.text(n.getLabel(), 0, r.dFont * curTextSize / r.textSize);
canvas.text(tree.getLabel(n), offX - curTextSize / 3 - s, offY - s - curTextSize / 3);
}
}
} else
{
// if (context.config().textRotation != 0)
// {
// canvas.rotate(-PApplet.radians(context.config().textRotation));
// }
if (alignRight)
{
canvas.textAlign(canvas.RIGHT, canvas.BASELINE);
registerPoint(canvas, n, 0, -curTextSize / 2);
registerPoint(canvas, n, -dx, curTextSize / 2);
} else
{
canvas.textAlign(canvas.LEFT, canvas.BASELINE);
registerPoint(canvas, n, 0, -curTextSize / 2);
registerPoint(canvas, n, dx, curTextSize / 2);
}
// if (context.config().textRotation != 0)
// {
// canvas.rotate(PApplet.radians(context.config().textRotation));
// }
if (actuallyRender)
canvas.text(tree.getLabel(n), 0, 0 + r.dFont * curTextSize / r.textSize);
}
if (actuallyRender)
n.lastTextSize = curTextSize;
return new float[] { dx, curTextSize };
}
private float textSizeForNode(BasicTreeRenderer r, PhyloNode n)
{
String always = n.getAnnotation(UsefulConstants.LABEL_ALWAYSSHOW);
if (always == null)
always = n.getAnnotation(UsefulConstants.LABEL_ALWAYSSHOW_ALT);
boolean alwaysShow = false;
if (always != null && always.equals("1"))
alwaysShow = true;
if (context.config().hideAllLabels && !alwaysShow)
return 0;
float thisRowSize = r.getTextSize() * context.config().textScaling * n.bulgeFactor;
if (context.config().showAllLabels) // If showing all labels, don't do the mintext setting.
return r.getTextSize() * context.config().textScaling;
// thisRowSize = Math.max(thisRowSize, context.config().minTextSize);
if (thisRowSize < context.config().minTextSize)
thisRowSize = context.config().minTextSize;
return thisRowSize;
}
static int textColor(PhyloNode n)
{
if (r.getTree().isCollapsed(n))
{
return context.config().getTextColor().brighter(128).getRGB();
}
if (n.isNHX())
{
int c = Color.black.getRGB();
String labelColor = n.getAnnotation(LABEL_COLOR);
if (labelColor == null)
labelColor = n.getAnnotation(LABEL_COLOR_ALT);
String tax = n.getAnnotation(TAXON_ID);
String spec = n.getAnnotation(SPECIES_NAME);
if (labelColor != null)
{
c = Color.parseColor(labelColor).getRGB();
} else if (tax != null && context.config().colorSpecies)
{
c = taxonColorMap.get(tax).intValue();
} else if (spec != null && context.config().colorSpecies)
{
c = taxonColorMap.get(spec);
}
return c;
} else
{
return context.config().getTextColor().getRGB();
}
}
public void positionText(BasicTreeRenderer r, PhyloNode n, TextField tf)
{
// float imgW = renderImage(r, n, 0, 0, textSize,false);
// if (imgW > 0)
// imgW += RenderConstants.labelSpacing*r.rowSize;
float dX = 0;
float curTextSize = n.lastTextSize;
float dY = r.dFont * curTextSize / r.textSize;
float textWidth = (float) n.unitTextWidth * curTextSize;
PGraphics canvas = null;
if (canvas != null)
{
dX += nr.render(canvas, n, false, true)[0];
dX += ir.render(canvas, n, false, true)[0];
}
float x = n.getX() + r.getNodeRadius() + r.getNormalLineWidth() * 2;
float y = n.getY();
tf.setTextSize(curTextSize);
tf.setWidth(textWidth);
tf.setPositionByBaseline(x + dX, y + dY);
}
}
public static class CigarRender extends RenderItem
{
static Pattern p = Pattern.compile("(\\d*?)([MD])", Pattern.CASE_INSENSITIVE);
public float[] render(PGraphics canvas, PhyloNode n, boolean actuallyRender, boolean preTransformed)
{
super.render(canvas, n, actuallyRender, preTransformed);
boolean alignRight = false;
if (n.getTextAlign() == PhyloNode.ALIGN_RIGHT)
alignRight = true;
String cigarLine = null;
cigarLine = n.getAnnotation(UsefulConstants.CIGAR);
if (cigarLine == null)
return ZEROES;
AlignmentBlocks abs = parseCigarBreakpoints(cigarLine);
List<Integer> bps = abs.blocks;
int length = abs.totalLength;
// Calculate how much to scale the aligned blocks.
float thisRowSize = cigarSizeForNode(r, n);
float totalWidth = context.config().cigarScaling * thisRowSize;
float widthPerBp = totalWidth / (float) length;
if (actuallyRender)
{
for (int i = 0; i < bps.size() - 1; i += 2)
{
int lo = bps.get(i);
int hi = bps.get(i + 1);
float loX = lo * widthPerBp;
float hiX = hi * widthPerBp;
canvas.noStroke();
canvas.fill(alignmentColor(n));
canvas.rect(loX, -thisRowSize / 2, hiX - loX, thisRowSize);
}
}
return new float[] { totalWidth, thisRowSize };
}
private AlignmentBlocks parseCigarBreakpoints(String cigarLine)
{
// Turn a cigar line into an array of match start and end points.
Matcher m = p.matcher(cigarLine);
int count = 0;
ArrayList<Integer> breakpoints = new ArrayList<Integer>();
while (m.find())
{
String numS = m.group(1);
int num = 1;
if (numS.length() > 0)
num = Integer.parseInt(numS);
String type = m.group(2);
if (type.equals("M"))
breakpoints.add(count);
for (int i = 0; i < num; i++)
{
count++;
}
if (type.equals("M"))
breakpoints.add(count);
}
AlignmentBlocks ab = new AlignmentBlocks();
ab.totalLength = count;
ab.blocks = breakpoints;
return ab;
}
private float cigarSizeForNode(BasicTreeRenderer r, PhyloNode n)
{
float thisRowSize = r.getTextSize();
if (!context.config().showAllLabels)
thisRowSize = Math.max(thisRowSize, context.config().minTextSize);
// If we find a NHX image size annotation, scale accordingly.
float iMult = getFloatAnnotation(n, CIGAR_SIZE);
if (iMult > -1)
thisRowSize *= iMult;
return thisRowSize;
}
static int alignmentColor(PhyloNode n)
{
if (n.isNHX())
{
String labelColor = n.getAnnotation(ALIGNMENT_COLOR);
if (labelColor == null)
labelColor = n.getAnnotation(ALIGNMENT_COLOR_ALT);
String tax = n.getAnnotation(TAXON_ID);
String spec = n.getAnnotation(SPECIES_NAME);
if (labelColor != null)
{
int c = Color.parseColor(labelColor).getRGB();
return c;
}
}
return context.config().getAlignmentColor().getRGB();
}
}
static class AlignmentBlocks
{
int totalLength;
List<Integer> blocks;
}
}