Source code for pyexlatex.graphics.tikz.node.position.directions

from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
    from pyexlatex.graphics.tikz.node.node import Node
from pyexlatex.models.item import ItemBase
from pyexlatex.models.package import Package
from pyexlatex.graphics.tikz.library import TikZLibrary


[docs]class DirectionBase(ItemBase): """ Base class for relative positioning directions in TikZ, e.g. above, above right, etc. """ name = '<invalid, do not use DirectionBase directly>' is_vertical_direction: Optional[bool] = None
[docs] def __init__(self, direction: Optional['DirectionBase'] = None, of: Optional['Node'] = None, by: Optional[float] = None, combined_direction: bool = True): """ :param direction: :param of: :param by: :param combined_direction: In some usages of directions, they are specified together, e.g. above right=2cm and 3cm. In other usages, they are specified separately, e.g. above=2cm, right=3cm. When combined_direction=True, will use the former style, and when False will use the latter style """ self.child_direction = direction self.of = of self.by = by self.combined_direction = combined_direction self._validate() self._set_vertical_and_horizontal_by() self.init_data() self.data.packages.extend([ Package('tikz'), TikZLibrary('positioning') ])
def _validate(self): if self.child_direction is not None and self.of is not None: raise ValueError('must only pass of when there are no other child directions') if self.is_vertical_direction is None: raise ValueError('must set is_vertical_direction to True or False when subclassing DirectionBase') if self.child_direction is not None: if self.is_vertical_direction and self.child_direction.is_vertical_direction: raise ValueError('cannot combine vertical directions, must be a vertical and horizontal direction') if not self.is_vertical_direction and not self.child_direction.is_vertical_direction: raise ValueError('cannot combine horizontal directions, must be a vertical and horizontal direction') if self.of is not None and self.child_direction.of is not None: raise ValueError('cannot pass two of in nested directions') @property def full_direction(self) -> str: if self.child_direction is None: return f'{self.name}{self.of_str}' if self.is_vertical_direction: vertical_name = self.name horizontal_name = self.child_direction.name else: vertical_name = self.child_direction.name horizontal_name = self.name if self.has_combined_direction: return f'{vertical_name} {horizontal_name}{self.of_str}' # Handle non-combined direction return f'{vertical_name}{self.vertical_of_str}, {horizontal_name}{self.horizontal_of_str}' @property def has_combined_direction(self) -> bool: if self.child_direction is None: return self.combined_direction if not self.combined_direction or not self.child_direction.combined_direction: # Having False in either parent or child means entire is treated as False, since True is the default return False return True @property def of_str(self) -> str: if self.of is None and (self.child_direction is None or self.child_direction.of is None): if not self.by_str: return '' else: return f'={self.by_str}' return f'={self.by_str}of {self.of_label}' @property def vertical_of_str(self) -> Optional[str]: if self.has_combined_direction: return None if self.of is None and (self.child_direction is None or self.child_direction.of is None): if not self.vertical_by_str: return '' else: return f'={self.vertical_by_str}' return f'={self.vertical_by_str}of {self.of_label}' @property def horizontal_of_str(self) -> Optional[str]: if self.has_combined_direction: return None if self.of is None and (self.child_direction is None or self.child_direction.of is None): if not self.horizontal_by_str: return '' else: return f'={self.horizontal_by_str}' return f'={self.horizontal_by_str}of {self.of_label}' @property def of_label(self) -> Optional[str]: if self.of is None and (self.child_direction is None or self.child_direction.of is None): return None if self.of is not None: label = self.of.label elif self.child_direction is not None and self.child_direction.of is not None: label = self.child_direction.of.label else: return None return label @property def by_str(self) -> str: if self.by is None and (self.child_direction is None or self.child_direction.by is None): return '' out_str = '' if self.vertical_by_str is not None: out_str += self.vertical_by_str if self.horizontal_by_str is not None: out_str += self.horizontal_by_str return out_str @property def vertical_by_str(self) -> Optional[str]: if self.vertical_by is None: return None return f'{self.vertical_by}cm ' @property def horizontal_by_str(self) -> Optional[str]: if self.horizontal_by is None: return None horizontal_str = f'{self.horizontal_by}cm ' if self.has_combined_direction: horizontal_str = 'and ' + horizontal_str return horizontal_str def _set_vertical_and_horizontal_by(self): if self.by is None and (self.child_direction is None or self.child_direction.by is None): self.vertical_by = None self.horizontal_by = None return vertical_by, horizontal_by = None, None if _is_vertical_direction_and_has_by(self): vertical_by = self.by elif self.child_direction is not None and _is_vertical_direction_and_has_by(self.child_direction): vertical_by = self.child_direction.by if _is_horizontal_direction_and_has_by(self): horizontal_by = self.by elif self.child_direction is not None and _is_horizontal_direction_and_has_by(self.child_direction): horizontal_by = self.child_direction.by self.vertical_by = vertical_by self.horizontal_by = horizontal_by def __str__(self) -> str: return self.full_direction
[docs]class VerticalDirection(DirectionBase): is_vertical_direction = True
[docs]class HorizontalDirection(DirectionBase): is_vertical_direction = False
[docs]class Above(VerticalDirection): """ Used for placing nodes: construct with one node, pass as placement to another node, that second node will be placed above the first node. """ name = 'above'
[docs]class Below(VerticalDirection): """ Used for placing nodes: construct with one node, pass as placement to another node, that second node will be placed below the first node. """ name = 'below'
[docs]class Left(HorizontalDirection): """ Used for placing nodes: construct with one node, pass as placement to another node, that second node will be placed to the left of the first node. """ name = 'left'
def _is_vertical_direction_and_has_by(direction: 'DirectionBase') -> bool: if direction.is_vertical_direction is None: return False return direction.is_vertical_direction and direction.by is not None def _is_horizontal_direction_and_has_by(direction: 'DirectionBase') -> bool: if direction.is_vertical_direction is None: return False return not direction.is_vertical_direction and direction.by is not None