Coverage for source/plotting/plot_responsibility_chain_base.py: 93%
29 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-07-30 20:59 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-07-30 20:59 +0000
1# plotting/plot_responsibility_chain_base.py
3# global imports
4import matplotlib.pyplot as plt
5from abc import ABC, abstractmethod
6from reportlab.lib.pagesizes import letter
7from reportlab.lib.units import inch
8from typing import Optional
10# local imports
12class PlotResponsibilityChainBase(ABC):
13 """
14 Base class for implementing the responsibility chain pattern for plotting.
16 This class provides a framework for creating a chain of plotting handlers where
17 each handler can decide whether it can handle a specific plot request. If a handler
18 cannot process the request, it passes the request to the next handler in the chain.
19 All plotting chain implementations should inherit from this class and implement
20 the _can_plot and _plot methods.
21 """
23 # local constants
24 __KEY_STR: str = 'key'
25 __PLOT_DATA_STR: str = 'plot_data'
26 __next_chain_link: 'PlotResponsibilityChainBase' = None
28 # derived constants
29 _EXPECTED_FIGURE_SIZE = (int(letter[0] // inch - 1), int(letter[1] // inch - 2))
31 def plot(self, data) -> Optional[plt.Axes]:
32 """
33 Attempts to plot data by finding an appropriate handler in the chain.
35 Parameters:
36 data (dict): Dictionary containing 'key' identifying the plot type
37 and 'plot_data' containing the actual data to be plotted.
39 Returns:
40 Optional[plt.Axes]: The matplotlib Axes object if plotting was successful,
41 None if no handler in the chain could process the request.
42 """
44 key = data[self.__KEY_STR]
45 plot_data = data[self.__PLOT_DATA_STR]
47 if (self._can_plot(key)):
48 return self._plot(plot_data)
49 else:
50 if self.__next_chain_link is not None:
51 return self.__next_chain_link.plot(data)
52 else:
53 return None
55 def add_next_chain_link(self, next_chain_link: 'PlotResponsibilityChainBase'):
56 """
57 Adds the next handler to the responsibility chain.
59 Parameters:
60 next_chain_link (PlotResponsibilityChainBase): The next handler to process
61 requests if this handler cannot.
62 """
64 current_last_chain_link = self
65 while current_last_chain_link.__next_chain_link is not None:
66 current_last_chain_link = current_last_chain_link.__next_chain_link
68 current_last_chain_link.__next_chain_link = next_chain_link
70 @abstractmethod
71 def _can_plot(self, key: str) -> bool:
72 """
73 Determines if this handler can plot the given plot type.
75 This is an abstract method that should be implemented by all subclasses.
77 Parameters:
78 key (str): String identifier of the plot type.
80 Returns:
81 bool: True if this handler can process the plot request, False otherwise.
82 """
83 pass
85 @abstractmethod
86 def _plot(self, plot_data: dict) -> plt.Axes:
87 """
88 Generates the actual plot from the provided data.
90 This is an abstract method that should be implemented by all subclasses.
91 The implementation should create and return a matplotlib plot based on
92 the provided data.
94 Parameters:
95 plot_data (dict): Dictionary containing the data needed for plotting.
97 Returns:
98 plt.Axes: Matplotlib Axes object containing the generated plot.
99 """
101 pass