Source code for phasik.drawing.drawing

"""
Useful drawing functions
"""

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches
from matplotlib.collections import LineCollection
from matplotlib.colors import BoundaryNorm, ListedColormap

from phasik.utils.utils import get_extrema_of_binary_series

__all__ = [
    "plot_events",
    "plot_interval",
    "plot_phases",
    "threshold_plot",
    "plot_edge_series",
]


[docs]def plot_events( events, ax=None, text_y_pos=None, text_x_offset=0, period=None, n_periods=1, add_labels=True, orientation="vertical", zorder=-1, alpha=1, va="bottom", ): """Visualize the occurence of events as vertical lines. This function was designed to be used in complement to another function `plot_cluster_sets` that draws objects over time (horizontal axis). The vertical lines are drawn at the horizontal value corresponding to the time of occurrence of the event. Parameters ---------- events : list of tuples (time, name, line_style) * time - time at which the event occurred * name - the name of the event * line_style - any string accepted by matplotlib.lines.Line2D.set_linestyle ax : matplotlib.Axes, optional Axes on which to plot the events text_y_pos : float, optional Height at which to place the name of the event (default None) text_x_offset : float, optional Distance along x-axis by which to offset the placement of the event name (default 0) period: float or None, optional Length of time of one period, if events repeat periodically. n_periods : int, optional Number of periods to draw, if events repeat periodically. add_labels : bool, optional Wether to display the label of each vertical line, True by default. orientation : {"vertical, horizontal"}, optonal Orientation of the lines marking the events. Default: "horizontal". zorder : float, optional Zorder of the lines marking the events. Default: -1. alpha : float Transparency of the lines marking the events. Default: 1. va : str, optional Direction with respect to which doing the vertical alignment. Default: "bottom" Returns ------- matplotlib.axes.Axes The axis object to draw on See Also -------- plot_phases plot_cluster_sets Examples -------- >>> import phasik as pk >>> events = [(5, "START", "dashed"), (33, "bud", "dashed"), (36, "ori", "dashed")] >>> cluster_sets.plot(axs=(ax1, ax2), with_silhouettes=True) >>> pk.plot_events(events, ax=ax1) """ if ax is None: ax = plt.gca() if text_y_pos is None: text_y_pos = 1.01 * ax.get_ylim()[1] if text_x_offset < 0: text_x_offset = -text_x_offset for event in events: time, name, line_style = event if orientation == "vertical": ax.axvline( x=time, c="k", ls=line_style, label=name, zorder=zorder, alpha=alpha, ) text_x_pos = time - text_x_offset if time > 0 else time + text_x_offset if add_labels: ax.text( text_x_pos, text_y_pos, name, fontsize="small", rotation=90, va=va, ha="center", ) elif orientation == "horizontal": ax.axhline( y=time, c="k", ls=line_style, label=name, zorder=zorder, alpha=alpha, ) else: print( "WARNING: wrong orientation, must be one of {'vertical', 'horizontal'}" ) if period is not None and n_periods > 1: # repeat events over n periods for k in range(1, n_periods): for event in events: time, name, line_style = event time += period * k if orientation == "vertical": ax.axvline( x=time, c="k", ls=line_style, label=name, zorder=zorder, alpha=alpha, ) text_x_pos = ( time - text_x_offset if time > 0 else time + text_x_offset ) if add_labels: ax.text( text_x_pos, text_y_pos, name, fontsize="small", rotation=90, va=va, ha="center", ) elif orientation == "horizontal": ax.axhline( y=time, c="k", ls=line_style, label=name, zorder=zorder, alpha=alpha, ) else: print( "WARNING: wrong orientation, must be one of {'vertical', 'horizontal'}" ) return ax
[docs]def plot_phases(phases, ax=None, y_pos=None, ymin=0, ymax=1, t_offset=0, color="k"): """Visualize temporal phases as shaded intervals This function was designed to be used in complement to another function `plot_cluster_sets` that draws objects over time (horizontal axis). The phases are drawn as shaded regions spanning the time interval corresponding to the phases. Parameters ---------- phases : list of tuples (start_time, end_time, name) The start time, end time, and name of each phase to visualize ax : matplotlib.Axes Axes on which to plot the phases y_pos : float or None, optional Height at which to place the name of the phase ymin : float, optional Height at which to start shaded region (default 0) ymax : float, optional Height at which to stop shaded region (default 1) t_offset : float, optional Offset of phase on the time axis color : color Color to draw the intervals in. Returns ------- matplotlib.axes.Axes The axis object to draw on See Also -------- plot_events plot_cluster_sets Examples -------- >>> import phasik as pk >>> phases = [(0, 35, "G1"), (35, 70, "S"), (70, 78, "G2")] >>> cluster_sets.plot(axs=(ax1, ax2), with_silhouettes=True) >>> pk.plot_phases(phases, ax=ax1, y_pos=0.05, ymax=0.1) """ if ax is None: ax = plt.gca() y_pos = y_pos if y_pos is not None else 1.01 y_lim = ax.get_ylim() absolute_y_pos = y_lim[0] + y_pos * (y_lim[1] - y_lim[0]) for i, phase in enumerate(phases): start_time, end_time, name = phase start_time += t_offset end_time += t_offset mid_time = (start_time + end_time) / 2 alpha_interval = 0.5 / len(phases) ax.axvspan( xmin=start_time, xmax=end_time, ymin=ymin, ymax=ymax, color=color, alpha=alpha_interval * (i + 1), ) ax.text( mid_time, absolute_y_pos, name, fontweight="bold", va="center", ha="center", ) return ax
[docs]def plot_interval( binary_series, times, y=0, peak=None, color="k", ax=None, zorder=None ): """Plot a binary series as a sequence of coloured intervals Specifically, draw rectangles to mark intervals where the binary series has value 1 (where it has value 0, do nothing). Parameters ---------- binary_series : ndarray 2D array of binary data to plot times : ndarray 1D array consisting of the corresponding time points y : float, optional Height (y-axis value) at which to plot the interval (default 0) peak : float, optional Time at which to mark the presence of peak with a red star. By default (None), not drawn. color : str, optional Color to use for the intervals (default 'k') ax : matplotlib.Axes, optional Axes to plot on zorder : int, optional Height of the z-axis on which to plot the interval (default None) Returns ------- matplotlib.axes.Axes The axis object to draw on Examples -------- >>> import phasik as pk >>> binary_series = [1, 1, 1, 0, 0, 1, 1, 0] >>> times = list(range(8)) >>> pk.plot_interval(binary_series, times, peak=2) """ if ax is None: ax = plt.gca() xmins, xmaxs = get_extrema_of_binary_series(binary_series, times) rect_height = 0.5 for xmin, xmax in zip(xmins, xmaxs): rect = patches.Rectangle( (xmin, y), xmax - xmin, rect_height, fill=True, color=color, zorder=zorder, ) ax.add_patch(rect) if peak is not None: ax.plot(peak, y + rect_height / 2, "r*") return ax
[docs]def threshold_plot( x, y, threshold, color_below_threshold, color_above_threshold, ax=None ): """ Plot values above a certain threshold in a particular colour Parameters ---------- x : array 1D array of values to plot along x-axis y : array 1D array of values to plot along y-axis threshold : float Only plot in colour the points (x,y) with y >= threshold color_below_threshold : color Colour to use for points below threshold color_above_threshold : list colors Colour to use for points above threshold ax : matplotlib.Axes, optional Axes to use Returns ------- line_collection : matplotlib LineCollection """ if ax is None: ax = plt.gca() # Create a colormap for red, green and blue and a norm to color # f' < -0.5 red, f' > 0.5 blue, and the rest green cmap = ListedColormap([color_below_threshold, color_above_threshold]) norm = BoundaryNorm([np.min(y), threshold, np.max(y)], cmap.N) # Create a set of line segments so that we can color them individually # This creates the points as a N x 1 x 2 array so that we can stack points # together easily to get the segments. The segments array for line collection # needs to be numlines x points per line x 2 (x and y) points = np.array([x, y]).T.reshape(-1, 1, 2) segments = np.concatenate([points[:-1], points[1:]], axis=1) # Create the line collection object, setting the colormapping parameters. # Have to set the actual values used for colormapping separately. line_collection = LineCollection(segments, cmap=cmap, norm=norm) line_collection.set_array(y) ax.add_collection(line_collection) ax.set_xlim(np.min(x), np.max(x)) ax.set_ylim(np.min(y) * 1.1, np.max(y) * 1.1) return line_collection
[docs]def plot_edge_series(temporal_network, edges, ax=None, **kwargs): """Draw time series of edge weights, for the specified edges Parameters ---------- temporal_network : pk.TempNet Temporal network edges: list of str List of edges to plot ax : matplotlib.Axes, optional Axes to use **kwargs : Other parameters to pass to matplotlib's plot Returns ------- matplotlib.axes.Axes The axis object to draw on Examples -------- >>> import phasik as pk >>> pk.plot_edge_series(temporal_network, ["A-B", "B-C"]) """ times = temporal_network.times if ax is None: ax = plt.gca() for edge in edges: ax.plot(times, temporal_network.edge_timeseries()[edge], **kwargs) return ax