Coverage for cc_modules/cc_trackerhelpers.py: 40%

1"""

2 camcops_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

30 from enum import Enum

31 from typing import List, Optional, Union

32

33 import numpy as np

34

35

36 DEFAULT_TRACKER_ASPECT_RATIO = 2.0 # width / height

37

38

39 class 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

50 class 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

76 class 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

87 class 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

135 def 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

155 def 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

164 def 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

189 def 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

209 def 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