Source code for varats.plot.plot
"""Base plot module."""
import abc
import logging
import typing as tp
from pathlib import Path
import matplotlib.pyplot as plt
from varats.paper.case_study import CaseStudy
from varats.plot.plots import PlotRegistry
LOG = logging.getLogger(__name__)
[docs]class PlotDataEmpty(Exception):
"""Throw if there was no input data for plotting."""
[docs]class Plot(metaclass=PlotRegistry):
"""An abstract base class for all plots generated by VaRA-TS."""
def __init__(self, name: str, **kwargs: tp.Any) -> None:
self.__name = name
self.__style = "classic"
self.__saved_extra_args = kwargs
@property
def name(self) -> str:
"""
Name of the current plot.
Test:
>>> Plot('test').name
'test'
"""
return self.__name
@property
def style(self) -> str:
"""
Current plot style.
Test:
>>> Plot('test').style
'classic'
"""
return self.__style
@style.setter
def style(self, new_style: str) -> None:
"""Access current style of the plot."""
self.__style = new_style
@property
def plot_kwargs(self) -> tp.Any:
"""
Access the kwargs passed to the initial plot.
Test:
>>> p = Plot('test', foo='bar', baz='bazzer')
>>> p.plot_kwargs['foo']
'bar'
>>> p.plot_kwargs['baz']
'bazzer'
"""
return self.__saved_extra_args
[docs] @staticmethod
def supports_stage_separation() -> bool:
"""True, if the plot supports stage separation, i.e., the plot can be
drawn separating the different stages in a case study."""
return False
[docs] @abc.abstractmethod
def plot(self, view_mode: bool) -> None:
"""Plot the current plot to a file."""
[docs] def show(self) -> None:
"""Show the current plot."""
try:
self.plot(True)
except PlotDataEmpty:
LOG.warning(f"No data for project {self.plot_kwargs['project']}.")
return
plt.show()
plt.close()
[docs] def plot_file_name(self, filetype: str) -> str:
"""
Get the file name this plot; will be stored to when calling save.
Args:
filetype: the file type for the plot
Returns:
the file name the plot will be stored to
Test:
>>> p = Plot('test', project='bar')
>>> p.plot_file_name('svg')
'bar_test.svg'
>>> p = Plot('foo', project='bar', plot_case_study=CaseStudy('baz', 42))
>>> p.plot_file_name('png')
'baz_42_foo.png'
"""
plot_ident = ''
if self.plot_kwargs.get('plot_case_study', None):
case_study: CaseStudy = self.plot_kwargs['plot_case_study']
plot_ident = f"{case_study.project_name}_{case_study.version}_"
elif 'project' in self.plot_kwargs:
plot_ident = f"{self.plot_kwargs['project']}_"
sep_stages = ''
if self.supports_stage_separation(
) and self.plot_kwargs.get('sep_stages', None):
sep_stages = 'S'
return f"{plot_ident}{self.name}{sep_stages}.{filetype}"
[docs] def save(
self, path: tp.Optional[Path] = None, filetype: str = 'svg'
) -> None:
"""
Save the current plot to a file.
Args:
path: The path where the file is stored (excluding the file name).
filetype: The file type of the plot.
"""
try:
self.plot(False)
except PlotDataEmpty:
LOG.warning(f"No data for project {self.plot_kwargs['project']}.")
return
if path is None:
plot_dir = Path(self.plot_kwargs["plot_dir"])
else:
plot_dir = path
# TODO (se-passau/VaRA#545): refactor dpi into plot_config.
plt.savefig(
plot_dir / self.plot_file_name(filetype),
dpi=1200,
bbox_inches="tight",
format=filetype
)
plt.close()
[docs] @abc.abstractmethod
def calc_missing_revisions(self, boundary_gradient: float) -> tp.Set[str]:
"""
Calculate a list of revisions that could improve precisions of this
plot.
Args:
boundary_gradient: The maximal expected gradient in percent between
two revisions, every thing that exceeds the
boundary should be further analyzed.
"""