Coverage for python/lsst/analysis/tools/actions/plot/gridPlot.py: 30%
58 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-03 02:47 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-03 02:47 -0700
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/>.
22from __future__ import annotations
24__all__ = ("GridPlot", "GridPanelConfig")
26from typing import TYPE_CHECKING
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
33from ...interfaces import PlotAction, PlotElement
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
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 )
52class GridPlot(PlotAction):
53 """Plot a series of plot elements onto a regularly spaced grid."""
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 )
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)
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])
104 xList = x.split(",") if (x := self.xDataKeys.get(index)) else None
105 valList = self.valsGroupBy[index].split(",")
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]]
118 _ = self.panels[index].plotElement(data=newData, ax=ax, **kwargs)
120 if self.panels[index].title is not None:
121 ax.set_title(**self.panels[index].title, y=self.panels[index].titleY)
123 plt.tight_layout()
124 fig.show()
125 return fig
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.")