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

1""" 

2camcops_server/cc_modules/cc_trackerhelpers.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

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. 

15 

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. 

20 

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/>. 

23 

24=============================================================================== 

25 

26**Helper representations for trackers.** 

27 

28""" 

29 

30from enum import Enum 

31from typing import List, Optional, Union 

32 

33import numpy as np 

34 

35 

36DEFAULT_TRACKER_ASPECT_RATIO = 2.0 # width / height 

37 

38 

39class LabelAlignment(Enum): 

40 """ 

41 Enum representing figure label alignment. 

42 """ 

43 

44 center = "center" 

45 top = "top" 

46 bottom = "bottom" 

47 baseline = "baseline" 

48 

49 

50class TrackerLabel(object): 

51 """ 

52 Representation of a label on a 

53 :class:`camcops_server.cc_modules.cc_tracker.Tracker` figure. 

54 """ 

55 

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 

71 

72 def __str__(self) -> str: 

73 return f"{self.y}: {self.label}" 

74 

75 

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 """ 

81 

82 def __init__(self, y: float, label: str): 

83 self.y = y 

84 self.label = label 

85 

86 

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 """ 

93 

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 

133 

134 

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. 

141 

142 Args: 

143 start: starting value 

144 stop: stopping value 

145 num: number of values to return 

146 endpoint: include the endpoint? 

147 

148 Returns: 

149 list of floats 

150 

151 """ 

152 return np.linspace(start, stop, num, endpoint=endpoint, dtype=float) 

153 

154 

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)) 

162 

163 

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`! 

169 

170 Args: 

171 start: starting value 

172 stop: stopping value (INCLUSIVE if endpoint is True) 

173 step: step size 

174 endpoint: bool 

175 

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)) 

187 

188 

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? 

198 

199 Returns: 

200 a list of simple numerical TrackerAxisTick objects 

201 

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 

207 

208 

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? 

218 

219 Returns: 

220 a list of simple numerical TrackerAxisTick objects 

221 

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