Source code for varats.plot.plot
"""Base plot module."""
import abc
import logging
import typing as tp
from functools import reduce
from pathlib import Path
import matplotlib.pyplot as plt
import varats.paper.case_study
from varats.plot.plots import PlotConfig
from varats.utils.git_util import FullCommitHash
if tp.TYPE_CHECKING:
from varats.paper.case_study import CaseStudy # pylint: disable=W0611
LOG = logging.getLogger(__name__)
[docs]
class PlotDataEmpty(Exception):
"""Throw if there was no input data for plotting."""
[docs]
class Plot:
"""An abstract base class for all plots generated by VaRA-TS."""
NAME = "Plot"
PLOTS: tp.Dict[str, tp.Type['Plot']] = {}
def __init__(self, plot_config: PlotConfig, **kwargs: tp.Any) -> None:
self.__plot_config = plot_config
self.__saved_extra_args = kwargs
@classmethod
def __init_subclass__(
cls, *, plot_name: tp.Optional[str], **kwargs: tp.Any
) -> None:
"""
Register concrete plots.
Args:
plot_name: name for the plot; if ``None``, do not register the plot
"""
super().__init_subclass__(**kwargs)
if plot_name:
cls.NAME = plot_name
cls.PLOTS[plot_name] = cls
[docs]
@staticmethod
def get_plot_types_help_string() -> str:
"""
Generates help string for visualizing all available plots.
Returns:
a help string that contains all available plot names.
"""
return "The following plots are available:\n " + "\n ".join(
list(Plot.PLOTS)
)
[docs]
@staticmethod
def get_class_for_plot_type(plot_type: str) -> tp.Type['Plot']:
"""
Get the class for plot from the plot registry.
Args:
plot_type: The name of the plot.
Returns: The class implementing the plot.
"""
if plot_type not in Plot.PLOTS:
raise LookupError(
f"Unknown plot '{plot_type}'.\n" +
Plot.get_plot_types_help_string()
)
plot_cls = Plot.PLOTS[plot_type]
return plot_cls
@property
def name(self) -> str:
"""
Name of the current plot.
Test:
>>> Plot(PlotConfig.from_kwargs(view=False)).name
'Plot'
"""
return self.NAME
@property
def plot_config(self) -> PlotConfig:
"""Plot config for this plot."""
return self.__plot_config
@property
def plot_kwargs(self) -> tp.Any:
"""
Access the kwargs passed to the initial plot.
Test:
>>> p = Plot(PlotConfig.from_kwargs(view=False),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("No data for the current 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(PlotConfig.from_kwargs(view=False),project='bar')
>>> p.plot_file_name('svg')
'bar_Plot.svg'
>>> from varats.paper.case_study import CaseStudy
>>> p = Plot(PlotConfig.from_kwargs(view=False),\
project='bar', case_study=CaseStudy('baz', 42))
>>> p.plot_file_name('png')
'baz_42_Plot.png'
"""
plot_ident = ''
if 'case_study' in self.plot_kwargs:
case_study = self.plot_kwargs['case_study']
if isinstance(case_study, varats.paper.case_study.CaseStudy):
plot_ident = f"{case_study.project_name}_{case_study.version}_"
else:
plot_ident = reduce(
lambda x, y: f'{x}{y.project_name}_', case_study, ''
)
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, plot_dir: Path, filetype: str = 'svg') -> None:
"""
Save the current plot to a file.
Args:
plot_dir: the path where the file is stored (excluding file name)
filetype: the file type of the plot
"""
try:
self.plot(False)
except PlotDataEmpty:
LOG.warning("No data for this plot.")
return
plt.savefig(
plot_dir / self.plot_file_name(filetype),
dpi=self.plot_config.dpi(),
bbox_inches="tight",
format=filetype
)
plt.close()
[docs]
@abc.abstractmethod
def calc_missing_revisions(
self, boundary_gradient: float
) -> tp.Set[FullCommitHash]:
"""
Calculate a list of revisions that could improve precisions of this
plot.
Raises an :class:`~varats.utils.exceptions.UnsupportedOperation` if not
supported by a plot.
Args:
boundary_gradient: The maximal expected gradient in percent between
two revisions, every thing that exceeds the
boundary should be further analyzed.
"""