Closed Dichromatism closed 3 years ago
Hi! Sorry for the long post, I though I might as well be thorough 😅
If I understand correctly what you want to do (I assume we are talking about trees in "rooted" style), I would say you have multiple options here:
This is probably the easiest:
The result should be something like this (sorry about the image quality, GitHub does not let me upload an SVG file):
You could use the Group labels module the way it is "meant" to be used:
One of the tutorials does something very similar and has a lot more detail: Displaying the names of the groups
The result should be something like this:
Similar to the above, but "hacks" the Group labels module in a creative way:
Group1
with value Group 1
to the LCA of group 1, attribute Group2
with value Group 2
to the LCA of group 2, etc).The result should be something like this:
Finally, you could go Rambo and use a Custom script Plot action to draw the rectangles. I think this is actually a good problem to start working with custom scripts in TreeViewer, because it is not too hard but not entirely trivial either. Here is a commented code example:
using PhyloTree;
using System.Collections.Generic;
using VectSharp;
using TreeViewer;
using System;
namespace adf9bf2c0252d4c83949458a07eadc17a
{
//Do not change class name
public static class CustomCode
{
//Do not change method signature
public static Point[] PerformPlotAction(TreeNode tree, Dictionary<string, Point> coordinates, Graphics graphics, InstanceStateData stateData)
{
//Define the groups we want to highlight by their common ancestor (you can suppy more taxon names, if needed)
TreeNode lcaOfGroup1 = tree.GetLastCommonAncestor("Taxon1", "Taxon5");
TreeNode lcaOfGroup2 = tree.GetLastCommonAncestor("Taxon16", "Taxon18");
TreeNode lcaOfGroup3 = tree.GetLastCommonAncestor("Taxon11", "Taxon18");
//Highlight each group. Since group 2 is a subset of group 3, you want to highlight group 3 first (so that the
//highlight for group 2 is drawn after, i.e. over, the highlight for group 3)
//The arguments we are passing to the "HighlightGroup" function (defined below) are:
// * The node to highlight
// * The colour with which to highlight it
// * The dictionary containing the coordinates of all the nodes in the tree (as it was supplied to us by the
// Coordinates module)
// * The graphics surface on which the plot is drawn
HighlightGroup(lcaOfGroup1, Colour.FromRgb(216, 245, 255), coordinates, graphics);
HighlightGroup(lcaOfGroup3, Colour.FromRgb(225, 182, 150), coordinates, graphics);
HighlightGroup(lcaOfGroup2, Colour.FromRgb(170, 227, 206), coordinates, graphics);
//This bit would be useful to update the bounds of the whole tree plot; since the things we are drawing are
//contained within the area of the tree anyways, we do not need to bother with this.
Point topLeft = new Point();
Point bottomRight = new Point();
return new Point[] { topLeft, bottomRight };
}
//Function to highlight a group in a certain colour
private static void HighlightGroup(TreeNode lca, Colour colour, Dictionary<string, Point> coordinates, Graphics graphics)
{
//First of all, determine the bounds of the area occupied by the specified group
//Initialise variables
double minX = double.MaxValue;
double maxX = double.MinValue;
double minY = double.MaxValue;
double maxY = double.MinValue;
//Iterate over the descendants of the LCA (LCA included)
foreach (TreeNode descendant in lca.GetChildrenRecursiveLazy())
{
//Get the coordinates of the node
Point pt = coordinates[descendant.Id];
//Update the bounds
minX = Math.Min(minX, pt.X);
maxX = Math.Max(maxX, pt.X);
minY = Math.Min(minY, pt.Y);
maxY = Math.Max(maxY, pt.Y);
}
//Now we know that all descendants of the specified node are contained in a rectangular area whose top-left
//corner is at (minX, minY) and whose width is maxX - minX and height is maxY - minY. We just need to draw the
//rectangle
//Margins so that the rectangle is not too tight
double marginLeft = 10;
double marginRight = 0.5;
double marginY = 7;
//Fill the rectangle with the specified colour.
graphics.FillRectangle(minX - marginLeft, minY - marginY, maxX - minX + marginLeft + marginRight, maxY - minY + 2 * marginY, colour);
}
}
}
Of course, this gives you much more flexibility, because now you are not limited to rectangles and can tweak every feature of the plot. You can also use this approach for trees in "unrooted" or "circular" style (naturally, in this case, you will want to use something more clever than a bounding rectangle shape). In the demo for VectSharp you can find a lot more examples of what you can do on a Graphics
object.
The result should be something like this:
In this ZIP file you can find the tree files I used to generate these plots, so that you can get an idea of how to set up the various modules.
Let me know if this makes sense or something is not clear!
Thank's a lot. I have tried and learnt your advises and checked your nexus files. I think they are useful to me. Maybe I need more time on your VectSharp module and try to draw a highlighted "circular" tree. Besides, I am appreciate for your brilliant work.
Glad you like my program!
The approach to highlight groups in a circular tree is essentially the same actually (i.e., use the Group labels modules and tweak the distance and height so that it looks nice). For example, this is equivalent to option 3 above:
Adapting the custom script is slightly more complicated, because we need to convert the Cartesian coordinates to polar and back, but it's just a maths problem. Here is how you could do this (you just need to replace the HighlightGroup
method, the rest remains the same):
//Function to highlight a group in a certain colour
private static void HighlightGroup(TreeNode lca, Colour colour, Dictionary<string, Point> coordinates, Graphics graphics)
{
//First of all, determine the bounds of the area occupied by the specified group
//In this case we need to work with polar coordinates (i.e. r and theta)
//Get a list of all the leaves that descend from the node
List<TreeNode> leaves = lca.GetLeaves();
//Coordinates of the "first" leaf in the group
Point pt1 = coordinates[leaves[0].Id];
//This holds the angle of the "first" leaf in the group. We use the Atan2 function to convert from Cartesian
//coordinates to polar coordinates
double theta1 = Math.Atan2(pt1.Y, pt1.X);
//Same thing for the "last" leaf in the group
Point pt2 = coordinates[leaves[leaves.Count - 1].Id];
double theta2 = Math.Atan2(pt2.Y, pt2.X);
//Make sure that theta2 is greater than theta1 (this might not be the case if the group straddles the angle at PI/180°)
if (theta2 < theta1)
{
theta2 += 2 * Math.PI;
}
//r for the leaves; we want to pick the one corresponding to the leaf that is furthest away from the centre, so
//we iterate over all the leaves
double rOuter = double.MinValue;
foreach (TreeNode leaf in leaves)
{
Point pt = coordinates[leaf.Id];
double r = Math.Sqrt(pt.X * pt.X + pt.Y * pt.Y);
rOuter = Math.Max(r, rOuter);
}
//Compute r for the LCA (we don't care about theta)
Point ptLCA = coordinates[lca.Id];
double rInner = Math.Sqrt(ptLCA.X * ptLCA.X + ptLCA.Y * ptLCA.Y);
//Now we know that all descendants of the specified node are contained within a circular crown going from rInner
//to rOuter, in a sector going from theta1 to theta2. We just need to draw it.
//Margins so that the shade is not too tight
double marginInner = 10;
double marginOuter = 0.5;
double marginTheta = 0.1;
rInner -= marginInner;
rOuter += marginOuter;
theta1 -= marginTheta;
theta2 += marginTheta;
//We can draw the shape using a GraphicsPath
GraphicsPath path = new GraphicsPath();
//Starting point - we need to convert back the polar coordinates to cartesian
path.MoveTo(rInner * Math.Cos(theta1), rInner * Math.Sin(theta1));
//Inner arc from theta1 to theta2
path.Arc(new Point(0, 0), rInner, theta1, theta2);
//Line to the outer arc
path.LineTo(rOuter * Math.Cos(theta2), rOuter * Math.Sin(theta2));
//Outer arc from theta2 to theta1
path.Arc(new Point(0, 0), rOuter, theta2, theta1);
//Close the path, drawing the last segment from the outer arc to the inner arc
path.Close();
//Fill the path with the specified colour.
graphics.FillPath(path, colour);
}
In this ZIP file you can find the equivalent of examples 3 and 4 above adapted for a circular tree.
Also, I forgot to say this earlier, but you can obviously combine e.g. 2 and 3 or 2 and 4 to show the group names as well as highlighting the groups.
Thank you! Your answer is very clear and I can draw trees following your comment.
The idea of combining 2 and 4 is just what I want and it works very well!
Hi! I am trying to use TreeViewer and have a question on color shades on clusters. Are there any modules that can draw color shades (or big "blocks") onto taxa or monophyletic clades? Which I mean is to draw a rectangular block consisting all of the branches or the names of one clade in a rectangular tree, aiming to emphasize. Sometimes I think it is necessary to draw different colored shades especially for there are a lot of different taxa. Thank you!