Coverage for python/lsst/analysis/tools/actions/plot/gridPlot.py: 30%

58 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-23 13:08 +0000

1# This file is part of analysis_tools. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("GridPlot", "GridPanelConfig") 

25 

26from typing import TYPE_CHECKING 

27 

28import matplotlib.pyplot as plt 

29from lsst.pex.config import Config, ConfigDictField, DictField, Field, ListField 

30from lsst.pex.config.configurableActions import ConfigurableActionField 

31from matplotlib.gridspec import GridSpec 

32 

33from ...interfaces import PlotAction, PlotElement 

34 

35if TYPE_CHECKING: 35 ↛ 36line 35 didn't jump to line 36, because the condition on line 35 was never true

36 from lsst.analysis.tools.interfaces import KeyedData, PlotResultType 

37 

38 

39class GridPanelConfig(Config): 

40 plotElement = ConfigurableActionField[PlotElement]( 

41 doc="Plot element.", 

42 ) 

43 title = DictField[str, str]( 

44 doc="String arguments passed into ax.set_title() defining the plot element title.", 

45 ) 

46 titleY = Field[float]( 

47 doc="Y position of plot element title.", 

48 default=None, 

49 ) 

50 

51 

52class GridPlot(PlotAction): 

53 """Plot a series of plot elements onto a regularly spaced grid.""" 

54 

55 panels = ConfigDictField( 

56 doc="Plot elements.", 

57 keytype=int, 

58 itemtype=GridPanelConfig, 

59 ) 

60 numRows = Field[int]( 

61 doc="Number of rows.", 

62 default=1, 

63 ) 

64 numCols = Field[int]( 

65 doc="Number of columns.", 

66 default=1, 

67 ) 

68 xDataKeys = DictField[int, str]( 

69 doc="Dependent data definitions. The key of this dict is the panel ID. The values are keys of data " 

70 "to plot (comma-separated for multiple) where each key may be a subset of a full key.", 

71 default={}, 

72 ) 

73 valsGroupBy = DictField[int, str]( 

74 doc="Independent data definitions. The key of this dict is the panel ID. The values are keys of data " 

75 "to plot (comma-separated for multiple) where each key may be a subset of a full key.", 

76 ) 

77 figsize = ListField[float]( 

78 doc="Figure size.", 

79 default=[8, 8], 

80 ) 

81 dpi = Field[float]( 

82 doc="Dots per inch.", 

83 default=150, 

84 ) 

85 suptitle = DictField[str, str]( 

86 doc="String arguments passed into fig.suptitle() defining the figure title.", 

87 optional=True, 

88 ) 

89 

90 def __call__(self, data: KeyedData, **kwargs) -> PlotResultType: 

91 """Plot data.""" 

92 fig = plt.figure(figsize=self.figsize, dpi=self.dpi) 

93 if self.suptitle is not None: 

94 fig.suptitle(**self.suptitle) 

95 gs = GridSpec(self.numRows, self.numCols, figure=fig) 

96 

97 for row in range(self.numRows): 

98 for col in range(self.numCols): 

99 index = row * self.numCols + col 

100 if index not in self.valsGroupBy.keys(): 

101 continue 

102 ax = fig.add_subplot(gs[row, col]) 

103 

104 xList = x.split(",") if (x := self.xDataKeys.get(index)) else None 

105 valList = self.valsGroupBy[index].split(",") 

106 

107 for i, val in enumerate(valList): 

108 for key in data: 

109 newData = {} 

110 if val not in key: 

111 continue 

112 namedKey = self.panels[index].plotElement.valsKey 

113 newData[namedKey] = data[key] 

114 if xList is not None: 

115 namedKey = self.panels[index].plotElement.xKey 

116 newData[namedKey] = data[xList[i]] 

117 

118 _ = self.panels[index].plotElement(data=newData, ax=ax, **kwargs) 

119 

120 if self.panels[index].title is not None: 

121 ax.set_title(**self.panels[index].title, y=self.panels[index].titleY) 

122 

123 plt.tight_layout() 

124 fig.show() 

125 return fig 

126 

127 def validate(self): 

128 """Validate configuration.""" 

129 super().validate() 

130 if self.xDataKeys and len(self.xDataKeys) != self.numRows * self.numCols: 

131 raise RuntimeError("Number of xDataKeys keys must match number of rows * columns.") 

132 if len(self.valsGroupBy) != self.numRows * self.numCols: 

133 raise RuntimeError("Number of valsGroupBy keys must match number of rows * columns.")