package org.phylowidget.render;
import java.util.HashMap;
import java.util.List;
import org.andrewberman.ui.Color;
import org.andrewberman.ui.UIUtils;
import org.phylowidget.PhyloWidget;
import org.phylowidget.tree.PhyloNode;
import processing.core.PGraphics;
public class LayoutCircular extends LayoutBase
{
final static float STARTING_ANGLE = .75f * (float)Math.PI*2f;
int numLeaves;
float depthToLeaves;
PhyloNode root;
float rootX;
float rootY;
HashMap<PhyloNode, AngleRadius> nodeToAngleRadius = new HashMap<PhyloNode, AngleRadius>();
public synchronized void layoutImpl()
{
numLeaves = leaves.length;
curAngle = STARTING_ANGLE + context.config().layoutAngle / 360f * (float)Math.PI*2f;
double index = 0;
for (PhyloNode leaf : leaves)
{
index+= getLayoutMult(leaf)/2;
leaf.setTextAlign(PhyloNode.ALIGN_LEFT);
leafPosition(leaf, index);
// index+= getLayoutMult(leaf)/2;
index++;
}
branchPosition((PhyloNode) tree.getRoot());
root = (PhyloNode) tree.getRoot();
rootX = ((PhyloNode) tree.getRoot()).getX();
rootY = ((PhyloNode) tree.getRoot()).getY();
}
@Override
public synchronized void drawLine(PGraphics canvas, PhyloNode p, PhyloNode c)
{
// Find the radius
AngleRadius cAr = nodeToAngleRadius.get(c);
AngleRadius pAr = nodeToAngleRadius.get(p);
if (root == null)
return;
float rootX = root.getX();
float rootY = root.getY();
float cRad = getDistance(root, c);
float pRad = getDistance(root, p);
// System.out.println(rootX+" "+rootY);
// canvas.line(c.getRealX(),c.getRealY(),rootX+(float)Math.cos(cAr.angle)*pAr.radius*scaleX,rootY+(float)Math.sin(cAr.angle)*pAr.radius*scaleY);
canvas.ellipseMode(PGraphics.RADIUS);
canvas.noFill();
// System.out.println(pAr.radius*scaleX);
float loAngle = loAngle(cAr.angle,pAr.angle);
float hiAngle = hiAngle(cAr.angle,pAr.angle);
canvas.arc(rootX, rootY, pRad, pRad, loAngle, hiAngle);
if (!context.config().useBranchLengths || !tree.isLeaf(c))
{
if (UIUtils.isJava2D(canvas))
{
canvas.strokeCap(canvas.ROUND);
}
canvas.line(c.getX(), c.getY(), rootX + (float) Math.cos(cAr.angle) * pRad, rootY
+ (float) Math.sin(cAr.angle) * pRad);
} else
{
// Draw the same line, but only up until the "true" leaf distance.
float ratio = cAr.leafRadius / cAr.radius;
if (UIUtils.isJava2D(canvas))
{
canvas.strokeCap(canvas.ROUND);
}
canvas.line(rootX + (float) Math.cos(cAr.angle) * cAr.leafRadius * scaleX * drawScaleX, rootY
+ (float) Math.sin(cAr.angle) * cAr.leafRadius * scaleY * drawScaleY, rootX
+ (float) Math.cos(cAr.angle) * pRad, rootY + (float) Math.sin(cAr.angle) * pRad);
// Draw a grayer line.
canvas.strokeCap(canvas.SQUARE);
Color color = new Color(canvas.strokeColor);
int avg = (int)((color.getRed() + color.getGreen() + color.getBlue()) / 3.0);
avg = Math.max(avg,180);
canvas.stroke(new Color(avg,avg,avg).getRGB());
canvas.line(rootX + (float) Math.cos(cAr.angle) * cAr.leafRadius * scaleX * drawScaleX, rootY
+ (float) Math.sin(cAr.angle) * cAr.leafRadius * scaleY * drawScaleY, c.getX(),c.getY());
}
}
private float getDistance(PhyloNode a, PhyloNode b)
{
float aX = a.getX();
float aY = a.getY();
float bX = b.getX();
float bY = b.getY();
return (float) Math.sqrt((bX - aX) * (bX - aX) + (bY - aY) * (bY - aY));
}
private float branchPosition(PhyloNode n)
{
if (tree.isLeaf(n))
{
// If N is a leaf, then it's already been laid out.
float theta = nodeToAngleRadius.get(n).angle;
// setAngle(n,theta);
return theta;
} else
{
// If not:
// theta should be the average of its children's thetas
List children = tree.getChildrenOf(n);
float sum = 0;
float count = 0;
for (int i = 0; i < children.size(); i++)
{
PhyloNode child = (PhyloNode) children.get(i);
sum += branchPosition(child);
count++;
}
// Find the radius.
float radius = calcRadius(n);
float theta = (float) sum / (float) count;
// Convert radius and theta into x,y.
float x = (float) Math.cos(theta) * radius;
float y = (float) Math.sin(theta) * radius;
setPosition(n, x, y);
setAngle(n, theta);
nodeToAngleRadius.put(n, new AngleRadius(theta, radius));
return theta;
}
}
/*
* Find the smallest angle between two angles.
*/
private float angleBetween(float a, float b)
{
a = a % TWOPI;
b = b % TWOPI;
if (b - a > PI)
a += TWOPI;
if (a - b > PI)
b += TWOPI;
return (a+b)/2f;
}
/*
* Find the lowest (i.e. most counter-clockwise) angle of two.
*/
private float loAngle(float a, float b)
{
a = a % TWOPI;
b = b % TWOPI;
if (b - a > PI)
a += TWOPI;
if (a - b > PI)
b += TWOPI;
return (float)Math.min(a,b);
}
/*
* Find the lowest (i.e. most counter-clockwise) angle of two.
*/
private float hiAngle(float a, float b)
{
a = a % TWOPI;
b = b % TWOPI;
if (b - a > PI)
a += TWOPI;
if (a - b > PI)
b += TWOPI;
return (float)Math.max(a,b);
}
static float PI = (float)Math.PI;
static float TWOPI = (float)Math.PI*2;
float curAngle;
private void leafPosition(PhyloNode n, double index)
{
/**
* Set the leaf position.
*/
float theta = curAngle;
// curAngle += 1f / (float)numLeaves * (float)Math.PI*2f;
// curAngle = (float) index / (float) numLeaves * (float) Math.PI * 2f;
// theta += startingAngle;
// theta = theta % (float)Math.PI * 2f;
float radius = 1;
float leafRadius = 1;
if (context.config().useBranchLengths)
leafRadius = calcRadius(n);
float yPos = (float) Math.sin(theta) * radius;
float xPos = (float) Math.cos(theta) * radius;
// if (context.config().useBranchLengths)
// xPos = calcXPosition(n);
setPosition(n, xPos, yPos);
setAngle(n, theta);
nodeToAngleRadius.put(n, new AngleRadius(theta, radius, leafRadius));
}
private float calcRadius(PhyloNode n)
{
if (context.config().useBranchLengths)
{
if (tree.isRoot(n))
return 0;
float asdf = (float) tree.getHeightToRoot(n) / (float) tree.getMaxHeightToLeaf(tree.getRoot());
return asdf;
} else
{
if (tree.isRoot(n))
return 0;
float md = 1f - (float) tree.getMaxDepthToLeaf(n) / (float) tree.getMaxDepthToLeaf(tree.getRoot());
return md;
}
}
class AngleRadius
{
public float angle;
public float radius;
public float leafRadius;
public AngleRadius(float angle, float radius)
{
this.angle = angle;
this.radius = radius;
}
public AngleRadius(float angle, float radius, float leafRadius)
{
this(angle, radius);
this.leafRadius = leafRadius;
}
}
}