Coverage for python/lsst/analysis/tools/actions/plot/xyPlot.py: 36%
61 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-24 10:22 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-24 10:22 +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/>.
22from __future__ import annotations
24__all__ = ("XYPlot",)
26from typing import TYPE_CHECKING, Any, Mapping
28import matplotlib.pyplot as plt
29from lsst.pex.config import ChoiceField, DictField, Field, FieldValidationError
31from ...interfaces import PlotAction, Vector
32from .plotUtils import addPlotInfo
34if TYPE_CHECKING: 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true
35 from matplotlib.figure import Figure
37 from ...interfaces import KeyedData, KeyedDataSchema
40class XYPlot(PlotAction):
41 boolKwargs = DictField[str, bool](
42 doc="Keyword arguments to ax.errorbar that take boolean values",
43 default={},
44 optional=True,
45 )
47 numKwargs = DictField[str, float](
48 doc="Keyword arguments to ax.errorbar that take numerical (float or int) values",
49 default={},
50 optional=True,
51 )
53 strKwargs = DictField[str, str](
54 doc="Keyword arguments to ax.errorbar that take string values",
55 default={},
56 optional=True,
57 )
59 xAxisLabel = Field[str](
60 doc="The label to use for the x-axis.",
61 default="x",
62 )
64 yAxisLabel = Field[str](
65 doc="The label to use for the y-axis.",
66 default="y",
67 )
69 xScale = ChoiceField[str](
70 doc="The scale to use for the x-axis.",
71 default="linear",
72 allowed={scale: scale for scale in ("linear", "log", "symlog")},
73 )
75 yScale = ChoiceField[str](
76 doc="The scale to use for the y-axis.",
77 default="linear",
78 allowed={scale: scale for scale in ("linear", "log", "symlog")},
79 )
81 xLinThresh = Field[float](
82 doc=(
83 "The value around zero where the scale becomes linear in x-axis "
84 "when symlog is set as the scale. Sets the `linthresh` parameter "
85 "of `~matplotlib.axes.set_xscale`."
86 ),
87 default=1e-6,
88 optional=True,
89 )
91 yLinThresh = Field[float](
92 doc=(
93 "The value around zero where the scale becomes linear in y-axis "
94 "when symlog is set as the scale. Sets the `linthresh` parameter "
95 "of `~matplotlib.axes.set_yscale`."
96 ),
97 default=1e-6,
98 optional=True,
99 )
101 def setDefaults(self):
102 super().setDefaults()
103 self.strKwargs = {"fmt": "o"}
105 def validate(self):
106 if (len(set(self.boolKwargs.keys()).intersection(self.numKwargs.keys())) > 0) or (
107 len(set(self.boolKwargs.keys()).intersection(self.strKwargs.keys())) > 0
108 ):
109 raise FieldValidationError(self.boolKwargs, self, "Keywords have been repeated")
111 super().validate()
113 def getInputSchema(self) -> KeyedDataSchema:
114 base: list[tuple[str, type[Vector]]] = []
115 base.append(("x", Vector))
116 base.append(("y", Vector))
117 base.append(("xerr", Vector))
118 base.append(("yerr", Vector))
119 return base
121 def __call__(self, data: KeyedData, **kwargs) -> Figure:
122 self._validateInput(data)
123 return self.makePlot(data, **kwargs)
125 def _validateInput(self, data: KeyedData) -> None:
126 needed = set(k[0] for k in self.getInputSchema())
127 if not needed.issubset(data.keys()):
128 raise ValueError(f"Input data does not contain all required keys: {self.getInputSchema()}")
130 def makePlot(self, data: KeyedData, plotInfo: Mapping[str, str] | None = None, **kwargs: Any) -> Figure:
131 """Make the plot.
133 Parameters
134 ----------
135 data : `~pandas.core.frame.DataFrame`
136 The catalog containing various rho statistics.
137 **kwargs
138 Additional keyword arguments to pass to the plot
139 """
140 # Allow for multiple curves to lie on the same plot.
141 fig = kwargs.get("fig", None)
142 if fig is None:
143 fig = plt.figure(dpi=300)
144 ax = fig.add_subplot(111)
145 else:
146 ax = fig.gca()
148 ax.errorbar(
149 data["x"],
150 data["y"],
151 xerr=data["xerr"],
152 yerr=data["yerr"],
153 **self.boolKwargs, # type: ignore
154 **self.numKwargs, # type: ignore
155 **self.strKwargs, # type: ignore
156 )
157 ax.set_xlabel(self.xAxisLabel)
158 ax.set_ylabel(self.yAxisLabel)
159 if self.xScale == "symlog":
160 ax.set_xscale("symlog", linthresh=self.xLinThresh)
161 ax.fill_betweenx(y=data["y"], x1=-self.xLinThresh, x2=self.xLinThresh, color="gray", alpha=0.2)
162 else:
163 ax.set_xscale(self.xScale) # type: ignore
164 if self.yScale == "symlog":
165 ax.set_yscale("symlog", linthresh=self.yLinThresh)
166 ax.fill_between(x=data["x"], y1=-self.yLinThresh, y2=self.yLinThresh, color="gray", alpha=0.2)
167 else:
168 ax.set_yscale(self.yScale) # type: ignore
170 if plotInfo is not None:
171 fig = addPlotInfo(fig, plotInfo)
173 return fig