Coverage for cc_modules/cc_trackerhelpers.py: 40%
52 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 14:23 +0100
1"""
2camcops_server/cc_modules/cc_trackerhelpers.py
4===============================================================================
6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CamCOPS.
11 CamCOPS is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CamCOPS is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26**Helper representations for trackers.**
28"""
30from enum import Enum
31from typing import List, Optional, Union
33import numpy as np
36DEFAULT_TRACKER_ASPECT_RATIO = 2.0 # width / height
39class LabelAlignment(Enum):
40 """
41 Enum representing figure label alignment.
42 """
44 center = "center"
45 top = "top"
46 bottom = "bottom"
47 baseline = "baseline"
50class TrackerLabel(object):
51 """
52 Representation of a label on a
53 :class:`camcops_server.cc_modules.cc_tracker.Tracker` figure.
54 """
56 def __init__(
57 self,
58 y: float,
59 label: str,
60 vertical_alignment: LabelAlignment = LabelAlignment.center,
61 ):
62 """
63 Args:
64 y: Y axis (vertical) position
65 label: text for label
66 vertical_alignment: :class:`LabelAlignment` enum
67 """
68 self.y = y
69 self.label = label
70 self.vertical_alignment = vertical_alignment
72 def __str__(self) -> str:
73 return f"{self.y}: {self.label}"
76class TrackerAxisTick(object):
77 """
78 Representation of a Y-axis tick mark and associated label on a
79 :class:`camcops_server.cc_modules.cc_tracker.Tracker` figure.
80 """
82 def __init__(self, y: float, label: str):
83 self.y = y
84 self.label = label
87class TrackerInfo(object):
88 """
89 Tasks return one or more of these (one for each tracker to be shown), from
90 which :class:`camcops_server.cc_modules.cc_tracker.Tracker` displays are
91 created.
92 """
94 def __init__(
95 self,
96 value: float,
97 plot_label: str = None,
98 axis_label: str = None,
99 axis_min: float = None,
100 axis_max: float = None,
101 axis_ticks: Optional[List[TrackerAxisTick]] = None,
102 horizontal_lines: Optional[
103 Union[list[float], list[int], list[Union[int, float]]]
104 ] = None,
105 horizontal_labels: Optional[List[TrackerLabel]] = None,
106 aspect_ratio: Optional[float] = DEFAULT_TRACKER_ASPECT_RATIO,
107 ):
108 """
109 Args:
110 value: numerical value
111 plot_label: label for the whole plot
112 axis_label: label for the Y axis
113 axis_min: minimum value for the Y axis
114 axis_max: maximum value for the Y axis
115 axis_ticks: optional list of :class:`TrackerAxisTick` objects
116 describing where to put tick marks/labels on the Y axis
117 horizontal_lines: optional list of y values at which to draw
118 horizontal (dotted) lines
119 horizontal_labels: optional list of :class:`TrackerLabel` objects
120 indicating which additional labels to place on the main plot
121 (such as: to describe the meaning of the horizontal lines)
122 aspect_ratio: optional aspect ratio (width / height)
123 """
124 self.value = value
125 self.plot_label = plot_label
126 self.axis_label = axis_label
127 self.axis_min = axis_min
128 self.axis_max = axis_max
129 self.axis_ticks = axis_ticks or []
130 self.horizontal_lines = horizontal_lines or []
131 self.horizontal_labels = horizontal_labels or []
132 self.aspect_ratio = aspect_ratio
135def equally_spaced_ndarray(
136 start: float, stop: float, num: int, endpoint: bool = True
137) -> np.ndarray:
138 """
139 Produces equally spaced numbers. See
140 https://stackoverflow.com/questions/477486/how-to-use-a-decimal-range-step-value.
142 Args:
143 start: starting value
144 stop: stopping value
145 num: number of values to return
146 endpoint: include the endpoint?
148 Returns:
149 list of floats
151 """
152 return np.linspace(start, stop, num, endpoint=endpoint, dtype=float)
155def equally_spaced_float(
156 start: float, stop: float, num: int, endpoint: bool = True
157) -> List[float]:
158 """
159 Returns a float equivalent of :func:`equally_spaced_float` (q.v.).
160 """
161 return list(equally_spaced_ndarray(start, stop, num, endpoint=endpoint))
164def equally_spaced_int(
165 start: int, stop: int, step: int, endpoint: bool = True
166) -> List[int]:
167 """
168 Almost a synonym for :func:`range`!
170 Args:
171 start: starting value
172 stop: stopping value (INCLUSIVE if endpoint is True)
173 step: step size
174 endpoint: bool
176 Returns:
177 list of integers
178 """
179 if endpoint:
180 if start <= stop: # normal
181 range_stop = stop + 1
182 else: # counting backwards: start > stop
183 range_stop = stop - 1
184 else:
185 range_stop = stop
186 return list(range(start, range_stop, step))
189def regular_tracker_axis_ticks_float(
190 start: float, stop: float, num: int, endpoint: bool = True
191) -> List[TrackerAxisTick]:
192 """
193 Args:
194 start: starting value
195 stop: stopping value
196 num: number of values to return
197 endpoint: include the endpoint?
199 Returns:
200 a list of simple numerical TrackerAxisTick objects
202 """
203 ticks = [] # type: List[TrackerAxisTick]
204 for val in equally_spaced_ndarray(start, stop, num, endpoint=endpoint):
205 ticks.append(TrackerAxisTick(val, str(val)))
206 return ticks
209def regular_tracker_axis_ticks_int(
210 start: int, stop: int, step: int, endpoint: bool = True
211) -> List[TrackerAxisTick]:
212 """
213 Args:
214 start: starting value
215 stop: stopping value
216 step: step size
217 endpoint: include the endpoint?
219 Returns:
220 a list of simple numerical TrackerAxisTick objects
222 """
223 ticks = [] # type: List[TrackerAxisTick]
224 for val in equally_spaced_int(start, stop, step, endpoint=endpoint):
225 ticks.append(TrackerAxisTick(val, str(val)))
226 return ticks