Source code for draugr.visualisation.matplotlib_utilities.matplotlib_utilities

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = "Christian Heider Nielsen"
__doc__ = r"""

           Created on 17/07/2020
           """

import subprocess
from enum import Enum
from pathlib import Path
from typing import Any, Sequence, Union

import numpy
from cycler import Cycler
from matplotlib import patches, pyplot, rcParams
from matplotlib.legend_handler import HandlerErrorbar
from sorcery import assigned_names

from draugr.visualisation.matplotlib_utilities.quirks import auto_post_hatch
from draugr.visualisation.matplotlib_utilities.styles.annotation import (
    rt_ann_transform,
    semi_opaque_round_tight_bbox,
)
from draugr.visualisation.matplotlib_utilities.styles.cyclers import (
    monochrome_hatch_cycler,
    monochrome_line_cycler,
    simple_hatch_cycler,
)

__all__ = [
    "denormalise_minusoneone",
    "matplotlib_bounding_box",
    "remove_decoration",
    "use_monochrome_style",
    "decolorise_plot",
    "save_embed_fig",
    "latex_clean_label",
    "make_errorbar_legend",
    "annotate_point",
    "MatplotlibHorizontalAlignment",
    "MatplotlibVerticalAlignment",
]

from matplotlib.patches import Rectangle
from matplotlib.pyplot import legend

from warg import Number, passes_kws_to

# Cycler
##Addition
###Equal length Cycler s with different keys can be added to get the ‘inner’ product of two cycles
##Multiplication
###which gives the ‘outer product’ of the two cycles (same as itertools.prod() )

from matplotlib.axes import Axes, ErrorbarContainer


[docs]class MatplotlibHorizontalAlignment(Enum): """description""" (center, right, left) = assigned_names()
[docs]class MatplotlibVerticalAlignment(Enum): """description""" (center, top, bottom, baseline, center_baseline) = assigned_names()
[docs]def latex_clean_label(s: str) -> str: """Clean label for troublesome symbols""" return s.replace("_", " ") # .replace('\\',' ').replace('/',' ')
[docs]def make_errorbar_legend(ax: Axes = None) -> None: """size adjusted legend for error bars, default does not work well with different linestyles""" if ax is None: ax = pyplot.gca() ax.legend( handlelength=5, handleheight=3, handler_map={ErrorbarContainer: HandlerErrorbar(xerr_size=0.9, yerr_size=0.9)}, )
[docs]def annotate_point(ax: Axes, x: Sequence, y: Sequence, t: Any) -> None: """ :param ax: :param x: :param y: :param t: :return: """ if ax is None: ax = pyplot.gca() ax.annotate( f"{t:.2f}", (x, y), textcoords="offset points", fontsize="xx-small", bbox=semi_opaque_round_tight_bbox, annotation_clip=True, # see details https://github.com/matplotlib/matplotlib/issues/14354#issuecomment-523630316 clip_on=True, **rt_ann_transform, )
[docs]@passes_kws_to(pyplot.savefig) def save_embed_fig( path: Union[Path, str], bbox_inches: Union[Number, str] = "tight", transparent: bool = True, attempt_fix_empty_white_space: bool = False, post_process_crop: bool = False, ax: Axes = None, suffix: str = ".pdf", **kwargs, ) -> None: """Save fig for latex pdf embedding""" if attempt_fix_empty_white_space: # remove it # pyplot.gca().set_axis_off() # pyplot.axis('off') # this rows the rectangular frame # ax.get_xaxis().set_visible(False) # this removes the ticks and numbers for x axis # ax.get_yaxis().set_visible(False) # this removes the ticks and numbers for y axis if ax is None: ax = pyplot.gca() pyplot.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) pyplot.margins(0, 0) ax.xaxis.set_major_locator(pyplot.NullLocator()) ax.yaxis.set_major_locator(pyplot.NullLocator()) """ clip_box = Bbox(((0,0),(300,300))) for o in pyplot.findobj(): o.set_clip_on(True) o.set_clip_box(clip_box) """ if not isinstance(path, Path): path = Path(path) path_str = str(path.with_suffix(suffix)) pyplot.savefig( path_str, bbox_inches=bbox_inches, transparent=transparent, **kwargs, ) if ( post_process_crop and suffix == ".pdf" ): # Generally a good idea since matplotlib does not exclude invisible parts(eg. data points or anchors) of the plot. from pdfCropMargins import crop # pip install pdfCropMargins crop( [ "-p", "0", # remove all percentage of old margin "-a", "-5", # add 5bp margin around all path_str, ] )
[docs]def remove_decoration(ax: Axes) -> None: """description""" transparent = (1.0, 1.0, 1.0, 0.0) ax.w_xaxis.set_pane_color(transparent) ax.w_yaxis.set_pane_color(transparent) ax.w_zaxis.set_pane_color(transparent) ax.w_xaxis.line.set_color(transparent) ax.w_yaxis.line.set_color(transparent) ax.w_zaxis.line.set_color(transparent) ax.set_xticks([]) ax.set_yticks([]) ax.set_zticks([])
[docs]def denormalise_minusoneone( t: Union[numpy.ndarray, Number], coordinates: Union[numpy.ndarray, Number] ) -> Union[numpy.ndarray, Number]: """description""" return 0.5 * ((coordinates + 1.0) * t)
[docs]def decolorise_plot(ax_: Axes, inverted: bool = False) -> None: """ set black and white edge colors and face colors respectively. Converse if inverted. """ edge_color = "k" face_color = "w" if inverted: face_color, edge_color = edge_color, face_color pyplot.setp(ax_.artists, edgecolor=edge_color, facecolor=face_color) pyplot.setp(ax_.lines, color=edge_color)
[docs]def matplotlib_bounding_box( x: Number, y: Number, size: Number, color: str = "w" ) -> Rectangle: """description""" x = int(x - (size / 2)) y = int(y - (size / 2)) rect = patches.Rectangle( (x, y), size, size, linewidth=1, edgecolor=color, fill=False ) return rect
[docs]def use_monochrome_style( prop_cycler: Cycler = monochrome_line_cycler, # ONLY COLOR AND LINESTYLE MAKES SENSE FOR NOW, matplotlib seems very undone in this api atleast for bars ) -> None: """ :param prop_cycler: """ # from matplotlib.pyplot import axes, grid # from matplotlib import lines, markers # print(lines.lineStyles.keys(),markers.MarkerStyle.markers.keys()) # print(rcParams.keys()) # pyplot.style.use('default') # print(pyplot.style.available) # print(matplotlib.matplotlib_fname()) pyplot.style.use(Path(__file__).parent / "styles" / "monochrome.mplstyle") if pyplot.rcParams["text.usetex"]: try: report = subprocess.check_output("latex -v", stderr=subprocess.STDOUT) except FileNotFoundError as exc: msg = f'No tex: {"latex"}' # raise RuntimeError(msg) print(f"{msg}, disabling") pyplot.rcParams.update({"text.usetex": False}) rcParams.update({"axes.prop_cycle": prop_cycler})
# auto_post_print_dpi() if __name__ == "__main__": def asiuhda() -> None: """ :rtype: None """ use_monochrome_style() bar_styles = monochrome_hatch_cycler() fig, ax = pyplot.subplots(1, 1) for x in range(3): ax.bar(x, numpy.random.randint(2, 10), **next(bar_styles), label=f"{x}") legend() from draugr.visualisation.matplotlib_utilities.quirks import fix_edge_gridlines fix_edge_gridlines(ax) pyplot.show() def asiuh214da() -> None: """ :rtype: None """ pyplot.style.use(Path(__file__).parent / "styles" / "monochrome.mplstyle") line_styles = monochrome_line_cycler() fig, ax = pyplot.subplots(1, 1) for x in range(3): ax.plot(numpy.random.rand(10), **next(line_styles), label=f"{x}") legend() from draugr.visualisation.matplotlib_utilities.quirks import fix_edge_gridlines fix_edge_gridlines(ax) pyplot.show() def asiuasdashda() -> None: """ :rtype: None """ use_monochrome_style(prop_cycler=monochrome_hatch_cycler) fig, ax = pyplot.subplots(1, 1) for x in range(3): ax.bar(x, numpy.random.randint(2, 10), label=f"{x}") legend() from draugr.visualisation.matplotlib_utilities.quirks import fix_edge_gridlines fix_edge_gridlines(ax) auto_post_hatch(ax, simple_hatch_cycler) pyplot.show() def asiuhdsada_non() -> None: """ :rtype: None """ fig, ax = pyplot.subplots(1, 1) for x in range(3): ax.plot(numpy.random.rand(10), label=f"{x}") legend() pyplot.show() def asiuhd21412sada() -> None: """ :rtype: None """ use_monochrome_style() fig, ax = pyplot.subplots(1, 1) for x in range(3): ax.plot(numpy.random.rand(10), label=f"{x}") legend() from draugr.visualisation.matplotlib_utilities.quirks import fix_edge_gridlines fix_edge_gridlines(ax) pyplot.show() # asiuhda() # asiuasdashda() asiuhd21412sada()