Marsilea-viz / marsilea

Declarative creation of composable visualization for Python (Complex heatmap, Upset plot, Oncoprint and more~)
https://marsilea.rtfd.io/
MIT License
174 stars 6 forks source link

Assigning legends to specific areas of a combined plot #51

Open newtonharry opened 1 month ago

newtonharry commented 1 month ago

Hi,

I'm trying to separate the two legends to the right such that the top one is closer to the middle and the bottom one is closer to the bottom plot:

image

Here is the code for it:

from marsilea.plotter import Labels
import matplotlib.pyplot as plt

# Initialize the ZeroHeight canvas for the track
fragment_track = ma.ZeroHeight(10, name="track")

# Add the peak area plot (assuming you have peak_counts defined)
fragment_track.add_bottom(
    mp.Area(
        peak_counts,  # Replace with your actual peak count data
        color="#4F6D7A",
        add_outline=False,
        alpha=1,
    ),
    name="peak"
)

# Add a title to the canvas
fragment_track.add_title("A", align="left", pad=0.4, fontsize=20)

# fragment_track.render()
colors = ['black', '#6a5acd', '#77DD77', '#FF5733', "#FFFF00"]  # Define distinct colors for different ranges
custom_cmap = ListedColormap(colors)
# Create the fragment cell heatmap
fragment_cell_heatmap = ma.CatHeatmap(downsampled_matrix.toarray(), cmap=custom_cmap, label="Read Count", height=10, width=10, name="fragment_cell_heatmap")
fragment_cell_heatmap.group_rows(downsampled_cell_types, order=np.unique(downsampled_cell_types), spacing=0.015)

# Add cell type labels on the left with no rotation
fragment_cell_heatmap.add_left(
    ma.plotter.Chunk(np.unique(downsampled_cell_types), rotation=0, props={"fontsize":23})
)

# Add title to the heatmap
fragment_cell_heatmap.add_title("B", align="left", pad=0.4, fontsize=20)

# Create cosine similarity heatmap
similarities = cosine_similarity(downsampled_matrix.toarray().T)
cosine_similarity_heatmap = ma.Heatmap(similarities, cmap="inferno", width=17, height=7, label="Cosine Similarity", name="cosine_similarities")
cosine_similarity_heatmap.add_title("C", align="left", pad=0.4, fontsize=20)
#cosine_similarity_heatmap.render()

# Combine all components into one layout
comb = fragment_track / 2.5 / fragment_cell_heatmap / 2.5 / cosine_similarity_heatmap
comb.add_legends("right", pad=1, stack_by="column")
comb.render()

# Get the Axes object corresponding to the "track" and "peak" plot
ax = comb.get_ax("track", "peak")

# Explicitly show the x-axis and customize it
ax.set_axis_on()
ax.xaxis.set_visible(True)  # Ensure the x-axis is visible
ax.tick_params(axis='x', which='both', bottom=True, labelbottom=True)  # Ensure ticks and labels are shown

# Define the start and end genomic positions (to map the relative positions)
genomic_start = 58572445
genomic_end = 58573849
num_ticks = 7  # Number of tick positions you want to show

# Set relative tick positions (e.g., positions 1 to N)
relative_positions = np.linspace(0, len(peak_counts) - 1, num_ticks).astype(int)

# Set the corresponding genomic positions as the labels
genomic_labels = np.linspace(genomic_start, genomic_end, num_ticks).astype(int)
fragment_count_labels = np.linspace(0, max(peak_counts), 7).astype(int)
# Set the relative tick positions and map them to the genomic labels
ax.set_xticks(relative_positions)
ax.set_xticklabels(["chr19:" + str(label) for label in genomic_labels], rotation=40, fontsize=23)
ax.set_yticks(relative_positions)
ax.set_yticklabels([str(label) for label in fragment_count_labels], fontsize=23)
ax.set_ylabel("Read counts", fontsize=20)
ax.set_xlabel("Genomic position", fontsize=20)
ax.set_ylim(0, max(peak_counts) + 10)
ax.tick_params(axis='y', labelsize=20)

ax = comb.layout.layouts["fragment_cell_heatmap"].get_main_ax()[-1]
ax.set_axis_on()
ax.xaxis.set_visible(True)  # Ensure the x-axis is visible and the y-axis is not
ax.yaxis.set_visible(False)
ax.tick_params(axis='x', which='both', bottom=True, labelbottom=True)  # Ensure ticks and labels are shown
ax.spines['bottom'].set_position(('outward', 20))

ax.set_xticks(relative_positions)
ax.set_xticklabels(["chr19:" + str(label) for label in genomic_labels], rotation=40, fontsize=23)
ax.set_xlabel("Genomic position", fontsize=23)

ax = comb.layout.layouts["cosine_similarities"].get_main_ax()
ax.set_axis_on()
ax.xaxis.set_visible(True)  # Ensure the x-axis is visible and the y-axis is not
ax.yaxis.set_visible(True)
ax.tick_params(axis='x', which='both', bottom=True, labelbottom=True)  # Ensure ticks and labels are shown
ax.spines['bottom'].set_position(('outward', 20))
ax.spines['left'].set_position(('outward', 20))

ax.set_xticks(relative_positions)
ax.set_xticklabels(["chr19:" + str(label) for label in genomic_labels], rotation=40, fontsize=23)
ax.set_xlabel("Genomic position", fontsize=23)

ax.set_yticks(relative_positions)
ax.set_yticklabels(["chr19:" + str(label) for label in genomic_labels], rotation=0, fontsize=23)
ax.set_ylabel("Genomic position", fontsize=23)
Mr-Milk commented 1 month ago

This is a planned feature, we are redesigning the layout algorithm for concatenation of multiple plots.

newtonharry commented 1 month ago

This is a planned feature, we are redesigning the layout algorithm for concatenation of multiple plots.

Since this is based off matplotlib, could you suggest a temporary workaround?

Mr-Milk commented 1 month ago

Well you can simply add an empty canvas and draw legends on it.