Source code for ms_mint.plotly_tools

import logging

import numpy as np
import pandas as pd
import colorlover as cl

import plotly.graph_objects as go
import plotly.figure_factory as ff
import plotly.io as pio

from pathlib import Path as P
from collections.abc import Iterable
from plotly.subplots import make_subplots

from .tools import fn_to_label



[docs] def set_template(): """ A function that sets a template for plotly figures. """ pio.templates["draft"] = go.layout.Template( layout=dict(font={"size": 10}), ) pio.templates.default = "draft"
set_template()
[docs] def plotly_heatmap( df, normed_by_cols=False, transposed=False, clustered=False, add_dendrogram=False, name="", x_tick_colors=None, height=None, width=None, correlation=False, call_show=False, verbose=False, ): """ Creates an interactive heatmap from a dense-formated dataframe. :param df: Input data :type df: pandas.DataFrame :param normed_by_cols: Whether or not to normalize column vectors, defaults to False :type normed_by_cols: bool, optional :param transposed: Whether or not to transpose the generated image, defaults to False :type transposed: bool, optional :param clustered: Whether or not to apply hierarchical clustering or rows, defaults to False :type clustered: bool, optional :param add_dendrogram: Whether or not to show a dendrogram (only with `clustered=True`), defaults to False :type add_dendrogram: bool, optional :param title: Title for figure, defaults to "" :type title: str, optional :param x_tick_colors: Color of x-ticks, defaults to None :type x_tick_colors: str, optional :param height: Image height in pixels, defaults to None :type height: int, optional :param width: Image width in pixels, defaults to None :type width: int, optional :param correlation: Whether or not to convert the table to a correlation matrix, defaults to False :type correlation: bool, optional :param call_show: Whether or not to call fig.show() to show image in new browser tab, defaults to False :type call_show: bool, optional :param verbose: Whether or not to be loud, defaults to False :type verbose: bool, optional :return: Returns a plotly image object. :rtype: plotly.Figure """ max_is_not_zero = df.max(axis=1) != 0 non_zero_labels = max_is_not_zero[max_is_not_zero].index df = df.loc[non_zero_labels] colorscale = "Bluered" plot_attributes = [] if normed_by_cols: df = df.divide(df.max()).fillna(0) plot_attributes.append("normalized") if transposed: df = df.T if correlation: plot_type = "Correlation" df = df.corr() colorscale = [ [0.0, "rgb(165,0,38)"], [0.1111111111111111, "rgb(215,48,39)"], [0.2222222222222222, "rgb(244,109,67)"], [0.3333333333333333, "rgb(253,174,97)"], [0.4444444444444444, "rgb(254,224,144)"], [0.5555555555555556, "rgb(224,243,248)"], [0.6666666666666666, "rgb(171,217,233)"], [0.7777777777777778, "rgb(116,173,209)"], [0.8888888888888888, "rgb(69,117,180)"], [1.0, "rgb(49,54,149)"], ] else: plot_type = "Heatmap" if clustered: dendro_side = ff.create_dendrogram( df, orientation="right", labels=df.index.to_list(), color_threshold=0, colorscale=["black"] * 8, ) dendro_leaves = dendro_side["layout"]["yaxis"]["ticktext"] df = df.loc[dendro_leaves, :] if correlation: df = df[df.index] x = df.columns if clustered: y = dendro_leaves else: y = df.index.to_list() z = df.values heatmap = go.Heatmap(x=x, y=y, z=z, colorscale=colorscale) if name == "": title = "" else: title = f'{plot_type} of {",".join(plot_attributes)} {name}' # Figure without side-dendrogram if (not add_dendrogram) or (not clustered): fig = go.Figure(heatmap) fig.update_layout( {"title_x": 0.5}, title={"text": title}, yaxis={"title": "", "tickmode": "array", "automargin": True}, ) fig.update_layout({"height": height, "width": width, "hovermode": "closest"}) else: # Figure with side-dendrogram fig = go.Figure() for i in range(len(dendro_side["data"])): dendro_side["data"][i]["xaxis"] = "x2" for data in dendro_side["data"]: fig.add_trace(data) y_labels = heatmap["y"] heatmap["y"] = dendro_side["layout"]["yaxis"]["tickvals"] fig.add_trace(heatmap) fig.update_layout( { "height": height, "width": width, "showlegend": False, "hovermode": "closest", "paper_bgcolor": "white", "plot_bgcolor": "white", "title_x": 0.5, }, title={"text": title}, # X-axis of main figure xaxis={ "domain": [0.11, 1], "mirror": False, "showgrid": False, "showline": False, "zeroline": False, "showticklabels": True, "ticks": "", }, # X-axis of side-dendrogram xaxis2={ "domain": [0, 0.1], "mirror": False, "showgrid": True, "showline": False, "zeroline": False, "showticklabels": False, "ticks": "", }, # Y-axis of main figure yaxis={ "domain": [0, 1], "mirror": False, "showgrid": False, "showline": False, "zeroline": False, "showticklabels": False, }, ) fig["layout"]["yaxis"]["ticktext"] = np.asarray(y_labels) fig["layout"]["yaxis"]["tickvals"] = np.asarray( dendro_side["layout"]["yaxis"]["tickvals"] ) fig.update_layout( # margin=dict( l=50, r=10, b=200, t=50, pad=0 ), autosize=True, hovermode="closest", ) fig.update_yaxes(automargin=True) fig.update_xaxes(automargin=True) if call_show: fig.show(config={"displaylogo": False}) else: return fig
[docs] def plotly_peak_shapes( mint_results, mint_metadata=None, color='ms_file_label', # Add the new argument for specifying color column fns=None, col_wrap=1, peak_labels=None, legend=True, verbose=False, legend_orientation="v", call_show=False, palette='Plasma', ): """ Plot peak shapes of mint results. :param mint_results: DataFrame in Mint results format. :type mint_results: pandas.DataFrame :param mint_metadata: DataFrame in Mint metadata format, defaults to None. :type mint_metadata: pandas.DataFrame, optional :param color: Column name determining color-coding of plots, defaults to 'ms_file_label'. :type color: str, optional :param fns: Filenames to include, defaults to None. :type fns: list, optional :param col_wrap: Maximum number of subplot columns, defaults to 1. :type col_wrap: int, optional :param peak_labels: Peak-labels to include, defaults to None. :type peak_labels: list, optional :param legend: Whether to display legend, defaults to True. :type legend: bool, optional :param verbose: If True, prints additional details, defaults to False. :type verbose: bool, optional :param legend_orientation: Legend orientation, defaults to 'v'. :type legend_orientation: str, optional :param call_show: If True, displays the plot immediately, defaults to False. :type call_show: bool, optional :param palette: Color palette to use, defaults to 'Plasma'. :type palette: str, optional :return: Plotly Figure object or None if call_show is True. :rtype: plotly.graph_objs._figure.Figure or None """ mint_results = mint_results.copy() # Merge with metadata if provided if mint_metadata is not None: mint_results = pd.merge(mint_results, mint_metadata, left_on='ms_file_label', right_index=True) # Filter by filenames if fns is not None: fns = [fn_to_label(fn) for fn in fns] mint_results = mint_results[mint_results.ms_file_label.isin(fns)] else: fns = mint_results.ms_file_label.unique() # Filter by peak_labels if peak_labels is not None: if isinstance(peak_labels, str): peak_labels = [peak_labels] mint_results = mint_results[mint_results.peak_label.isin(peak_labels)] else: peak_labels = mint_results.results.peak_label.unique() # Handle colors based on metadata or fall back to default behavior colors = None if color: unique_hues = mint_results[color].unique() colors = get_palette_colors(palette, len(unique_hues)) color_mapping = dict(zip(unique_hues, colors)) if color == 'ms_file_label': hue_column = [color_mapping[fn] for fn in fns] else: # Existing logic remains the same for the else part hue_column = mint_results.drop_duplicates('ms_file_label').set_index('ms_file_label')[color].map(color_mapping).reindex(fns).tolist() else: hue_column = colors # Rest of the plotting process res = mint_results[mint_results.peak_max > 0] labels = mint_results.peak_label.unique() res = res.set_index(["peak_label", "ms_file_label"]).sort_index() # Calculate necessary number of rows n_rows = max(1, len(labels) // col_wrap) if n_rows * col_wrap < len(labels): n_rows += 1 fig = make_subplots( rows=max(1, n_rows), cols=max(1, col_wrap), subplot_titles=peak_labels ) for label_i, label in enumerate(peak_labels): for file_i, fn in enumerate(fns): try: x, y = res.loc[(label, fn), ["peak_shape_rt", "peak_shape_int"]] except KeyError as e: logging.warning(e) continue if not isinstance(x, Iterable): continue if isinstance(x, str): x = x.split(",") y = y.split(",") ndx_r = (label_i // col_wrap) + 1 ndx_c = label_i % col_wrap + 1 if len(x) == 1: mode = "markers" else: mode = "lines" trace_color = trace_color = hue_column[file_i] fig.add_trace( go.Scatter( x=x, y=y, name=P(fn).name, mode=mode, legendgroup=file_i, showlegend=(label_i == 0), marker_color=trace_color, text=fn, ), row=ndx_r, col=ndx_c, ) fig.update_xaxes(title_text="Scan time [s]", row=ndx_r, col=ndx_c) fig.update_yaxes(title_text="Intensity", row=ndx_r, col=ndx_c) # Layout updates if legend: fig.update_layout(legend_orientation=legend_orientation) fig.update_layout(showlegend=legend) fig.update_layout(height=400 * n_rows, title_text="Peak Shapes") if call_show: fig.show(config={"displaylogo": False}) else: return fig
[docs] def get_palette_colors(palette_name, num_colors): # Categories in the colorlover package categories = ["qual", "seq", "div"] num_colors = max(num_colors, 3) # Check in which category our palette resides for category in categories: if palette_name in cl.scales[f"{num_colors}"][category]: return cl.scales[f"{num_colors}"][category][palette_name] # If palette not found in any category, return a default one or raise an error return cl.scales[f"{num_colors}"]["qual"]["Paired"]