Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rotate the tree 180 degree #2

Closed
dongzhang0725 opened this issue Jan 27, 2024 · 7 comments
Closed

rotate the tree 180 degree #2

dongzhang0725 opened this issue Jan 27, 2024 · 7 comments
Labels
enhancement New feature or request

Comments

@dongzhang0725
Copy link

dongzhang0725 commented Jan 27, 2024

Dear developer,

Thank you for your previous suggestion. I discovered that phyTreeViz is a useful tool that can integrate Matplotlib components. Currently, I'm developing a function to create a tangled tree. To accomplish this, I first need to mirror the tree so that it faces left (i.e., rotate the tree 180 degrees). The second step is to obtain the x and y coordinates of the leaf labels in the mirrored tree. Do you have any suggested code for achieving this?

The resulting tree should resemble the following:

image

Sincerely,
Dong

@moshi4
Copy link
Owner

moshi4 commented Jan 28, 2024

There is no functionality in phyTreeViz to do what you want to do.
I have a feeling it would not be difficult to implement, so I will consider implementing it when I feel like it.

@moshi4 moshi4 mentioned this issue Jan 29, 2024
@moshi4
Copy link
Owner

moshi4 commented Jan 29, 2024

I implemented the orientation functionality you suggested in newly released v0.2.0. It was easier than I thought it would be.

Code Example

from phytreeviz import TreeViz, load_example_tree_file

# Plot tree in `left` orientation
tree_file = load_example_tree_file("medium_example.nwk")
tv = TreeViz(tree_file, orientation="left")
tv.savefig("example.png")

# x and y coordinates of the leaf labels?
# No further coordinate information exists in phyTreeViz
for leaf_label in tv.leaf_labels:
    leaf_line_tip_xy = tv.name2xy[leaf_label]
    rect_for_highlight = tv.name2rect[leaf_label]
    print(leaf_label, leaf_line_tip_xy, rect_for_highlight)

example.png
example

@dongzhang0725
Copy link
Author

Dear @moshi4 ,

Thank you very much for your update! The code to mirror a tree is quite elegant. However, I encountered an issue while attempting to create a tanglegram using the following code:

from phytreeviz import TreeViz, load_example_tree_file
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec

tree_file = load_example_tree_file("small_example.nwk")
tree_file2 = load_example_tree_file("small_example.nwk")

tv1 = TreeViz(tree_file, orientation="right")
tv2 = TreeViz(tree_file2, orientation="left")

fig = plt.figure(figsize=(9, 7))
gs = GridSpec(1, 2, width_ratios=[1,1])
ax1 = fig.add_subplot(gs[0,0])
ax2 = fig.add_subplot(gs[0,1])
tv1.plotfig(ax=ax1)
tv2.plotfig(ax=ax2)

fig.tight_layout()

plt.subplots_adjust(wspace=0, hspace=0)

Unfortunately, it seems that the labels of the two trees are overlapping:
image

Upon investigation, I discovered that this is due to the x-limit (xlim) of the axis extending only from the root to the furthest node, instead of encompassing the entire width of the tree including the labels. To address this, I attempted to extend the xlim to the farthest label using the code below:

from phytreeviz import TreeViz


class PS_Treeviz(TreeViz):

    def __init__(
            self,
            tree_data: str | Path | Tree,  # type: ignore
            # invert_: bool = False,
            orientation: str = "right",
            *,
            format: str = "newick",
            height: float = 0.5,
            width: float = 8,
            align_leaf_label: bool = False,
            ignore_branch_length: bool = False,
            leaf_label_size: float = 12,
            innode_label_size: float = 0,
            show_auto_innode_label: bool = True,
            leaf_label_xmargin_ratio: float = 0.01,
            innode_label_xmargin_ratio: float = 0.01,
            reverse: bool = False,
    ):
        super(PS_Treeviz, self).__init__(
            tree_data,
            orientation=orientation,
            format=format,
            height=height,
            width=width,
            align_leaf_label=align_leaf_label,
            ignore_branch_length=ignore_branch_length,
            leaf_label_size=leaf_label_size,
            innode_label_size=innode_label_size,
            show_auto_innode_label=show_auto_innode_label,
            leaf_label_xmargin_ratio=leaf_label_xmargin_ratio,
            innode_label_xmargin_ratio=innode_label_xmargin_ratio,
            reverse=reverse,
        )
        # for setting xlim
        self.tree_lengths: list = [self.max_tree_depth]

    @property
    def xlim(self) -> tuple[float, float]:
        """Axes xlim"""
        if self._orientation == "left":
            return (max(self.tree_lengths), 0)
        else:
            return (0, max(self.tree_lengths))

    def _plot_node_label(self, ax: Axes) -> None:
        """Plot tree node label

        Parameters
        ----------
        ax : Axes
            Matplotlib axes for plotting
        """
        node: Clade
        self.tree_lengths: list = [self.max_tree_depth]
        for node in self.tree.find_clades():
            # Get label x, y position
            x, y = self.name2xy[str(node.name)]
            # Get label size & xmargin
            if node.is_terminal():
                label_size = self._leaf_label_size
                label_xmargin_ratio = self._leaf_label_xmargin_ratio
            else:
                label_size = self._innode_label_size
                label_xmargin_ratio = self._innode_label_xmargin_ratio
            label_xmargin = self.max_tree_depth * label_xmargin_ratio
            # Set label x position with margin
            if node.is_terminal() and self._align_leaf_label:
                x = self.max_tree_depth + label_xmargin
            else:
                x += label_xmargin
            # Skip if 'label is auto set name' or 'no label size'
            if label_size <= 0:
                continue
            is_auto_innode_label = node.name in self._auto_innode_labels
            if not self._show_auto_innode_label and is_auto_innode_label:
                continue
            # Plot label
            text_kws = dict(size=label_size, ha="left", va="center_baseline")
            text_kws.update(self._node2label_props[str(node.name)])
            if self._orientation == "left":
                text_kws.update(ha="right")

            ax_text = ax.text(x, y, s=node.name, **text_kws)
            trans_bbox = self.ax.transData.inverted().transform_bbox(ax_text.get_window_extent())
            text_length = trans_bbox.width # trans_bbox.xmax - trans_bbox.xmin

            # get toot to text width
            if node.is_terminal():
                # text_length = self._get_texts_rect(node.name).get_width()
                length = x + text_length
                self.tree_lengths.append(length)
        self._init_axes(self.ax)    

tree_file = load_example_tree_file("small_example.nwk")
tree_file2 = load_example_tree_file("small_example.nwk")

tv1 = PS_Treeviz(tree_file, orientation="right")
tv2 = PS_Treeviz(tree_file2, orientation="left")

fig = plt.figure(figsize=(9, 7))
gs = GridSpec(1, 2, width_ratios=[1,1])
ax1 = fig.add_subplot(gs[0,0]) 
ax2 = fig.add_subplot(gs[0,1]) 

tv1.plotfig(ax=ax1)
tv2.plotfig(ax=ax2)

fig.tight_layout()

plt.subplots_adjust(wspace=0, hspace=0)

However, some overlaps persist:
image

It's worth noting that extending the xlim in this manner could also enhance our ability to draw alignment figures alongside the tree (related to #1 ssue). Interestingly, my modified code works seamlessly when there's only one tree:

tree_file = load_example_tree_file("small_example.nwk")

tv = PS_Treeviz(tree_file, orientation='right')
tv.show_branch_length(color="red")
tv.show_confidence(color="blue")
tv.show_scale_bar()

fig = plt.figure(figsize=(9, 7))

gs = GridSpec(1, 3, width_ratios=[4, 1, 1])
ax1 = fig.add_subplot(gs[0,0]) 
ax2 = fig.add_subplot(gs[0,1]) 
ax3 = fig.add_subplot(gs[0,2]) 
ax1.sharey(ax2)
ax2.sharey(ax3)
ax2.axis('off') 
ax2.grid(False)  
ax3.axis('off') 
ax3.grid(False)

def draw_piechart(ax, labels, sizes, center, radius, start_angle):
    total = sum(sizes)
    theta1 = start_angle
    for size in sizes:
        theta2 = theta1 + (size / total) * 360.0
        wedge = Wedge(center, radius, theta1, theta2, edgecolor=ColorCycler(), facecolor=ColorCycler())  
        ax.add_patch(wedge)
        theta1 = theta2

tv.plotfig(ax=ax1)
ax1.grid(True)  

categories = [1, 2, 3, 4, 5, 6, 7]
values = [25, 40, 30, 20, 40, 10, 30]
ax3.barh(categories, values, color='skyblue')  

labels = ['Category A', 'Category B', 'Category C', 'Category D']
sizes = [25, 30, 20, 25]
radius = 0.3
ax2.set_xlim(0, radius*2) 
start_angle = 0
ax2.set_aspect('equal', adjustable='box')
for leaf in tv.leaf_labels:
    x, y = tv.name2xy_center[leaf]
    center = (0.3, y)
    draw_piechart(ax2, labels, sizes, center, radius, start_angle)

x_total = tv.xlim[1] - tv.xlim[0]
y_total = tv.ylim[1] - tv.ylim[0]
in_axs_wh = 0.1 
radius = 0.5 
for node in tv.innode_labels:
    x, y = tv.name2xy_center[node]
    center = (0.5,0.5)
    x_ = (x-in_axs_wh/2)/x_total 
    y_ = y/y_total
    in_axs = ax1.inset_axes([x_,y_,in_axs_wh,in_axs_wh]) 
    in_axs.axis('off') 
    in_axs.grid(False)
    in_axs.set_aspect('equal', adjustable='box')
    draw_piechart(in_axs, labels, sizes, center, radius, start_angle)

fig.tight_layout() 

plt.subplots_adjust(wspace=0, hspace=0)

image

Do you have any insights into why the code works for one tree but fails for two trees? Additionally, do you have any suggestions for achieving the desired outcome more effectively?

On a side note, it appears that the scale bar is not displaying correctly in my figure. I'm uncertain about the cause of this issue.

Thank you for your patience and assistance!

Sincerely,

Dong

@moshi4
Copy link
Owner

moshi4 commented Jan 29, 2024

I think what you want to do is a challenging task in using matplotlib. With patchworklib, you may possibly be able to solve your problem.

Code Example1

pip install patchworklib

from phytreeviz import TreeViz, load_example_tree_file
import patchworklib as pw

tree_file = load_example_tree_file("small_example.nwk")
tv1 = TreeViz(tree_file, orientation="right")
tv1.highlight(["Homo_sapiens", "Pan_paniscus"], color="salmon")
tv2 = TreeViz(tree_file, orientation="left")
tv2.highlight(["Hylobates_moloch", "Nomascus_leucogenys"], color="skyblue")

ax1 = pw.Brick(figsize=(3, 7))
ax2 = pw.Brick(figsize=(3, 7))

tv1.plotfig(ax=ax1)
tv2.plotfig(ax=ax2)

ax12 = ax1 | ax2

ax12.savefig("example1.png")

example1.png

example

Code Example2

from phytreeviz import TreeViz, load_example_tree_file
import patchworklib as pw
from matplotlib.patches import Wedge
import random
random.seed(0)

# Plot tree (ax1)
tree_file = load_example_tree_file("small_example.nwk")
tv = TreeViz(tree_file, orientation="right")
tv.highlight(["Homo_sapiens", "Pan_paniscus"], color="salmon")
tv.show_branch_length(color="red")
tv.show_confidence(color="blue")
tv.show_scale_bar()

ax1 = pw.Brick(figsize=(3, 7))
tv.plotfig(ax=ax1)

# Plot piechart (ax2)
def draw_piechart(ax, sizes, center, radius):
    total = sum(sizes)
    theta1 = 0
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
    for idx, size in enumerate(sizes):
        theta2 = theta1 + (size / total) * 360.0
        wedge = Wedge(center, radius, theta1, theta2, edgecolor=colors[idx], facecolor=colors[idx])
        ax.add_patch(wedge)
        theta1 = theta2

ax2 = pw.Brick(figsize=(1, 7))
ax2.set_xlim(0, 1)
ax2.set_ylim(*tv.ylim)
ax2.set_axis_off()
ax2.set_aspect(aspect=1)
for i in range(len(tv.leaf_labels)):
    draw_piechart(ax2, [10, 20, 30, 40], center=(0.5, i + 1), radius=0.35)

# Plot bar (ax3)
ax3 = pw.Brick(figsize=(3, 7))
ax3.set_ylim(*tv.ylim)
ax3.set_axis_off()
y = list(range(1, len(tv.leaf_labels) + 1))
x = [random.randint(1, 10) for _ in range(len(tv.leaf_labels))]
ax3.barh(y, x, color="skyblue")

# Concatenate axes
ax123 = ax1 | ax2 | ax3

ax123.savefig("example2.png")

example2.png

example2

I can't advise you on anything more than this, as it is a difficult problem for me as well.
If you want to do more than this, you should use other libraries like ete or ggtree.

@dongzhang0725
Copy link
Author

dongzhang0725 commented Jan 30, 2024

Dear @moshi4,

Thank you for providing the suggested codes; they resolved my problem. I'm also delighted to discover patchworklib—it's a fantastic package and has been quite helpful.

If you want to do more than this, you should use other libraries like ete or ggtree.
As I'm in the process of integrating a Python package for phylogenetic tree annotation into PhyloSuite (https://github.com/dongzhang0725/PhyloSuite), a platform we've designed for molecular phylogenetic analysis, ggtree may not be suitable for our purposes. Regarding ete3, despite investing significant effort in exploring it and integrating it into PhyloSuite, I encountered its somewhat complex logical structure and unresolved bugs. Additionally, its circular tree function performed poorly, leading me to abandon it. I prefer phyTreeViz and pycirclize due to their relatively simple logical structures and their ability to integrate with Matplotlib's figure elements. Consequently, I've decided to integrate them into PhyloSuite for tree annotation. Once we've made substantial progress, I'd be delighted to share the code with you and extend an invitation for you to participate in this project, provided you're willing to do so.

A quick overview of the fundamental work for phyTreeViz we have completed:
20240130225530
We will cite phyTreeViz and pycirclize in the GUI in future updates.

One of my previous questions remains unresolved: On a side note, it appears that the scale bar is not displaying correctly in my figure. I'm uncertain about the cause of this issue..

I've noticed that in your code, there is a scale bar when using tv.show_scale_bar(). However, in my version, the same code fails to display the scale bar. I'm uncertain about the cause of this discrepancy.

@moshi4
Copy link
Owner

moshi4 commented Jan 31, 2024

One of my previous questions remains unresolved: On a side note, it appears that the scale bar is not displaying correctly in my figure. I'm uncertain about the cause of this issue..

I've noticed that in your code, there is a scale bar when using tv.show_scale_bar(). However, in my version, the same code fails to display the scale bar. I'm uncertain about the cause of this discrepancy.

Your use of phyTreeViz is out of the basic usage originally expected.
Therefore, it is difficult to advise you on the cause of the discrepancy you pointed out.

@dongzhang0725
Copy link
Author

dongzhang0725 commented Feb 1, 2024

Thank you @moshi4 , I discovered that the issue was caused by the size_vertical parameter of AnchoredSizeBar. When I set it to 0.1, I was able to see the bar.

image

@moshi4 moshi4 closed this as completed Feb 1, 2024
@moshi4 moshi4 added the enhancement New feature or request label May 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants