Source code for pyexlatex.figure.models.figure

import os
from typing import Union, List, Dict, Any, TYPE_CHECKING, Optional

from pyexlatex.typing import PyexlatexItems

if TYPE_CHECKING:
    from matplotlib.pyplot import Axes, Figure as PltFigure

from pyexlatex.figure.models.subfigure import Subfigure, Graphic
from pyexlatex.models.item import Item
from pyexlatex.models.caption import Caption
from pyexlatex.models.label import Label
from pyexlatex.logic.builder import build_figure_content
from pyexlatex.texgen.replacements.filename import latex_filename_replacements

from pyexlatex.models.commands.newenvironment import NewEnvironment
from pyexlatex.models.commands.begin import Begin
from pyexlatex.models.commands.end import End
from pyexlatex.models.environment import Environment
from pyexlatex.models.containeritem import ContainerItem

SubfigureOrGraphic = Union[Subfigure, Graphic]
SubfiguresOrGraphics = List[SubfigureOrGraphic]
PltFigureOrAxes = Union['Axes', 'PltFigure']
PltFigureOrAxesNameDict = Dict[str, PltFigureOrAxes]


[docs]class Figure(ContainerItem, Item): """ Used for creating latex figures from images. Currently the main usage is the Figure class created with the method Figure.from_dict_of_names_and_filepaths. Pass a dictionary where the keys are names for subfigures and the values are filepaths where the image for the subfigure is located. """ name = 'figure'
[docs] def __init__(self, subfigures: SubfiguresOrGraphics, caption: Optional[PyexlatexItems] = None, label: Optional[str] = None, centering: bool = True, position_str: Optional[str] = None, landscape: bool = False, short_caption: Optional[str] = None): self.subfigures = subfigures self.caption = Caption(caption, short_caption=short_caption) if caption else None self.label = Label(label) if label else None self.centering = centering self.landscape = landscape self._remove_subfigure_elevate_contents_to_figure_if_single_subfigure() self.add_data_from_content(self.subfigures) content = build_figure_content( self.subfigures, caption=self.caption, label=self.label, centering=self.centering, position_str=position_str ) super().__init__(self.name, content) if landscape: lfigure_def = NewEnvironment( 'lfigure', Begin('landscape') + Begin('figure'), End('figure') + End('landscape') ) self.data.begin_document_items.append(lfigure_def) self.env = Environment('lfigure')
def __repr__(self): return f'<Figure(subfigures={self.subfigures}, caption={self.caption})>' def __iter__(self): for subfigure in self.subfigures: yield subfigure def __getitem__(self, item): return self.subfigures[item]
[docs] def as_document(self, landscape=False): from pyexlatex.models.document import Document from pyexlatex.figure.packages import default_packages return Document(self, default_packages, landscape=landscape)
[docs] def to_pdf_and_move(self, as_document=True, outfolder: str=None, outname: str=None, landscape=False): from pyexlatex.logic.output.main import output_document_and_move from pyexlatex.models.document import Document to_output: Union[Figure, Document] to_output = self # Figure if as_document: to_output = self.as_document( # Document landscape=landscape if self.landscape == False else False # don't apply landscape twice ) if outfolder is None: outfolder = '.' if outname is None: outname = 'figure' else: outname = latex_filename_replacements(outname) output_document_and_move( to_output, outfolder, image_paths=self.data.filepaths, outname=outname, as_document=as_document, image_binaries=self.data.binaries )
[docs] @classmethod def from_dict_of_names_and_filepaths(cls, filepath_name_dict: dict, figure_name: str=None, position_str_name_dict: dict=None, **kwargs): """ Create a Figure from a dictionary where keys are names of subfigures and values are file paths :param filepath_name_dict: dictionary where keys are names of subfigures and values are the filepaths to the images for those subfigures. :param figure_name: name for overall figure :param position_str_name_dict: dictionary where keys are names of subfigures and values are the position strs for those figures, e.g. r'[t]{0.45\linewidth}' :param kwargs: kwargs for Figure :return: Figure """ # TODO [#3]: for Figure, add possibility of passing grid shape rather than actual position str if position_str_name_dict is None: position_str_name_dict = {} subfigures = [] for name, filepath in filepath_name_dict.items(): subfigures.append( Subfigure( filepath, caption=name, position_str=position_str_name_dict[name] if name in position_str_name_dict else r'[t]{0.45\linewidth}' ) ) return cls( subfigures, caption=figure_name, **kwargs )
[docs] @classmethod def from_dict_of_names_and_plt_figures(cls, plt_fig_name_dict: PltFigureOrAxesNameDict, sources_outfolder: str, source_filetype: str = 'pdf', figure_name: str=None, position_str_name_dict: dict=None, **kwargs): """ Create a Figure from a dictionary where keys are names of subfigures and values are matplotlib Figures or Axes :param plt_fig_name_dict: Key is display name in output figure, value is matplotlib axes or figure :param sources_outfolder: folder to output individual matplotlib figures :param source_filetype: Filetype for individual plt figures. The default is pdf. Use png or another image type if outputting complicated figures or performance may be affected when viewing the pdf. :param figure_name: name for overall figure :param position_str_name_dict: dictionary where keys are names of subfigures and values are the position strs for those figures, e.g. r'[t]{0.45\linewidth}' :param kwargs: kwargs for Figure :return: Figure """ filepath_name_dict = {} # store outputted filepaths of sources to pass to from_dict_of_names_and_filepaths for name, plt_figure_or_axes in plt_fig_name_dict.items(): plt_figure = _get_plt_figure_from_axes_or_figure(plt_figure_or_axes) outpath = os.path.join(sources_outfolder, f'{latex_filename_replacements(name)}.{source_filetype}') plt_figure.savefig(outpath) filepath_name_dict[name] = outpath return cls.from_dict_of_names_and_filepaths( filepath_name_dict, figure_name=figure_name, position_str_name_dict=position_str_name_dict, **kwargs )
def _remove_subfigure_elevate_contents_to_figure_if_single_subfigure(self): """ If there is a single subfigure, leaving in subfigure format results in very small output. Must strip out subfigure layer, leaving only a figure with a graphic, then it can fill the page. :return: """ if len(self.subfigures) != 1: return self.subfigures: List[Union[Subfigure, Graphic]] if hasattr(self.subfigures[0], 'graphic'): # got subfigure item_is_subfigure = True orig_graphic = self.subfigures[0].graphic else: # got graphic item_is_subfigure = False orig_graphic = self.subfigures[0] orig_width = self.subfigures[0].width # Elevate caption of sub-figure if there is no figure caption if self.caption is None and item_is_subfigure: self.caption = self.subfigures[0].caption # update width to original width graphic = Graphic(orig_graphic.filepaths[0], width=orig_width) # need to turn off centering to cover whole page self.centering = False self.subfigures = [graphic]
[docs] def to_graphic_list(self) -> List[Graphic]: graphics: List[Graphic] = [] for subfig_or_graphic in self.subfigures: if isinstance(subfig_or_graphic, Graphic): graphics.append(subfig_or_graphic) elif isinstance(subfig_or_graphic, Subfigure): graphics.append(subfig_or_graphic.graphic) else: raise ValueError(f'got other than Subfigure or Graphic in subfigures: ' f'{subfig_or_graphic} of type {type(subfig_or_graphic)}') return graphics
def _get_plt_figure_from_axes_or_figure(plt_axes_or_fig: PltFigureOrAxes) -> 'PltFigure': # Both axes and figure have the get_figure method, however for the figure, it will return None possible_figure = plt_axes_or_fig.get_figure() if possible_figure is None: return plt_axes_or_fig # had figure already else: return possible_figure # extracted figure from axes