axis.py (13331B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\graphs\axis.py 2 import math 3 import blue 4 import localization 5 from carbon.common.script.util.commonutils import StripTags 6 from signals import Signal 7 AXIS_TIGHT = 0 8 AXIS_FROM_ZERO = 1 9 AXIS_CUSTOM = 2 10 AXIS_FILTERED = 3 11 12 class AxisOrientation(object): 13 HORIZONTAL = 0 14 VERTICAL = 1 15 16 17 class InteractionLimit(object): 18 NONE = 0 19 DATA_RANGE = 1 20 DATA_AND_VIEWPORT_RANGE = 2 21 22 23 def GetStrippedLabel(path, **kwargs): 24 label = localization.GetByLabel(path, **kwargs) 25 if isinstance(label, basestring): 26 label = StripTags(label, stripOnly=['localized']) 27 return label 28 29 30 def GetRangeFromSequences(*sequences): 31 minv = None 32 maxv = None 33 for sequence in sequences: 34 if sequence: 35 v = min(sequence) 36 if minv is None: 37 minv = v 38 else: 39 minv = min(minv, v) 40 v = max(sequence) 41 if maxv is None: 42 maxv = v 43 else: 44 maxv = max(maxv, v) 45 46 return (minv, maxv) 47 48 49 def FixupRange(dataRange): 50 dataRange = (float(dataRange[0]), float(dataRange[1])) 51 if dataRange[0] > dataRange[1]: 52 dataRange = (dataRange[1], dataRange[0]) 53 if dataRange[0] == dataRange[1]: 54 dataRange = (dataRange[0], dataRange[0] + 0.1) 55 return dataRange 56 57 58 class BaseAxis(object): 59 60 def __init__(self, dataRange, viewportRange = (0.0, 1.0), visibleRange = None, behavior = AXIS_TIGHT, margins = (0.0, 0.0), interactionLimit = InteractionLimit.DATA_AND_VIEWPORT_RANGE, labelFormat = str): 61 self._dataRange = FixupRange(dataRange) 62 self._viewportRange = viewportRange 63 self._visibleRange = FixupRange(visibleRange or dataRange) 64 self._margins = margins 65 self._behavior = behavior 66 self._interactionLimit = interactionLimit 67 self._labelFormat = labelFormat 68 self.onChange = Signal(signalName='onChange') 69 self._UpdateVisibleRange() 70 71 def MapToView(self, value): 72 return (value - self._visibleRange[0]) / (self._visibleRange[1] - self._visibleRange[0]) 73 74 def MapFromView(self, value): 75 return self._visibleRange[0] + value * (self._visibleRange[1] - self._visibleRange[0]) 76 77 def MapToViewport(self, value): 78 v = self.MapToView(value) 79 return self._viewportRange[0] * (1 - v) + self._viewportRange[1] * v 80 81 def MapFromViewport(self, value): 82 v = (value - self._viewportRange[0]) / (self._viewportRange[1] - self._viewportRange[0]) 83 return self.MapFromView(v) 84 85 def MapSequenceToViewport(self, sequence): 86 visible = (self._viewportRange[1] - self._viewportRange[0]) / (self._visibleRange[1] - self._visibleRange[0]) 87 offset = self._viewportRange[0] - self._visibleRange[0] * visible 88 return (offset + value * visible for value in sequence) 89 90 def UpdateToViewportTransform(self, transform, column, dpiScaling = 1): 91 visible = (self._viewportRange[1] - self._viewportRange[0]) / (self._visibleRange[1] - self._visibleRange[0]) 92 offset = (self._viewportRange[0] - self._visibleRange[0] * visible) * dpiScaling 93 scale = visible * dpiScaling 94 if column == 0: 95 row0 = list(transform[0]) 96 row0[0] = scale 97 row2 = list(transform[2]) 98 row2[0] = offset 99 return (tuple(row0), transform[1], tuple(row2)) 100 else: 101 row1 = list(transform[1]) 102 row1[1] = scale 103 row2 = list(transform[2]) 104 row2[1] = offset 105 return (transform[0], tuple(row1), tuple(row2)) 106 107 def SetViewportRange(self, viewport): 108 if self._viewportRange == viewport: 109 return 110 self._viewportRange = viewport 111 self._OnChange() 112 113 def GetViewportRange(self): 114 return self._viewportRange 115 116 def GetViewportSize(self): 117 return self._viewportRange[1] - self._viewportRange[0] 118 119 def SetDataRange(self, dataRange): 120 self._dataRange = dataRange 121 self._UpdateVisibleRange() 122 self._OnChange() 123 124 def ZoomOn(self, visibleRange): 125 if visibleRange is None: 126 self._UpdateVisibleRange() 127 self._OnChange() 128 return 129 scope = self._dataRange[1] - self._dataRange[0] 130 if self._behavior == AXIS_FROM_ZERO: 131 self._visibleRange = (self._visibleRange[0], visibleRange[1] + scope * self._margins[1]) 132 elif self._behavior == AXIS_TIGHT: 133 self._visibleRange = (visibleRange[0] - scope * self._margins[0], visibleRange[1] + scope * self._margins[1]) 134 else: 135 self._visibleRange = visibleRange 136 self._visibleRange = FixupRange(self._visibleRange) 137 self._OnChange() 138 139 def GetDataRange(self): 140 return self._dataRange 141 142 def GetVisibleRange(self): 143 return self._visibleRange 144 145 def SetVisibleRange(self, visibleRange): 146 if self._visibleRange != visibleRange: 147 self._visibleRange = visibleRange 148 self._OnChange() 149 150 def GetBehavior(self): 151 return self._behavior 152 153 def SetBehavior(self, behavior): 154 if self._behavior == behavior: 155 return 156 self._behavior = behavior 157 self._UpdateVisibleRange() 158 self._OnChange() 159 160 def _UpdateVisibleRange(self): 161 scope = self._dataRange[1] - self._dataRange[0] 162 if self._behavior == AXIS_FROM_ZERO: 163 self._visibleRange = (min(self._dataRange[0], 0), self._dataRange[1] + scope * self._margins[1]) 164 elif self._behavior == AXIS_TIGHT: 165 self._visibleRange = (self._dataRange[0] - scope * self._margins[0], self._dataRange[1] + scope * self._margins[1]) 166 self._visibleRange = FixupRange(self._visibleRange) 167 168 def Zoom(self, amount, ratio = 0.5): 169 self._behavior = AXIS_CUSTOM 170 scope = self._visibleRange[1] - self._visibleRange[0] 171 amount *= scope 172 if not amount: 173 return 174 oldVisibleRange = self._visibleRange 175 self._visibleRange = (self._visibleRange[0] + amount * ratio, self._visibleRange[1] - amount * (1.0 - ratio)) 176 dataRange = abs(self.MapToView(self._dataRange[0]) - self.MapToView(self._dataRange[1])) 177 if dataRange > 200 and amount > 0 or dataRange < 0.01 and amount < 0: 178 self._visibleRange = oldVisibleRange 179 return 180 if self._visibleRange[1] <= self._visibleRange[0] or math.isinf(self._visibleRange[0]) or math.isinf(self._visibleRange[1]): 181 self._visibleRange = oldVisibleRange 182 return 183 if abs(self.MapToViewport(self._dataRange[0]) - self.MapToViewport(self._dataRange[1])) < 1: 184 self._visibleRange = oldVisibleRange 185 return 186 self._OnChange() 187 188 def Pan(self, amount): 189 scope = self._visibleRange[1] - self._visibleRange[0] 190 delta = amount * scope 191 if not delta: 192 return 193 self._behavior = AXIS_CUSTOM 194 if self._interactionLimit == InteractionLimit.DATA_AND_VIEWPORT_RANGE: 195 if self._visibleRange[1] + delta < self._dataRange[0]: 196 delta = self._dataRange[0] - self._visibleRange[1] 197 if self._visibleRange[0] + delta > self._dataRange[1]: 198 delta = self._dataRange[1] - self._visibleRange[0] 199 elif self._interactionLimit == InteractionLimit.DATA_RANGE: 200 if self._visibleRange[0] + delta < self._dataRange[0]: 201 delta = self._dataRange[0] - self._visibleRange[0] 202 if self._visibleRange[1] + delta > self._dataRange[1]: 203 delta = self._dataRange[1] - self._visibleRange[1] 204 self._visibleRange = (self._visibleRange[0] + delta, self._visibleRange[1] + delta) 205 self._OnChange() 206 207 def GetTicks(self): 208 return () 209 210 def GetTickLabel(self, tickValue): 211 return self._labelFormat(tickValue) 212 213 def _OnChange(self): 214 self.onChange(self) 215 216 def GetRangeText(self, start, end): 217 return str(end - start) 218 219 220 class CategoryAxis(BaseAxis): 221 222 def __init__(self, dataPoints, *args, **kwargs): 223 super(CategoryAxis, self).__init__((-0.5, len(dataPoints) - 0.5), *args, **kwargs) 224 self._dataPoints = dataPoints 225 226 def GetDataPoints(self): 227 return self._dataPoints 228 229 def SetDataPoints(self, dataPoints, updateRange = True): 230 self._dataPoints = dataPoints 231 if updateRange: 232 dataRange = FixupRange((-0.5, len(dataPoints) - 0.5)) 233 self.SetDataRange(dataRange) 234 235 def MapDataPointsToViewport(self): 236 return self.MapSequenceToViewport(xrange(len(self._dataPoints))) 237 238 def GetTicks(self): 239 return range(len(self._dataPoints)) 240 241 def GetTickLabel(self, tickValue): 242 return self._labelFormat(self._dataPoints[tickValue]) 243 244 def GetRangeText(self, start, end): 245 return '' 246 247 248 class AutoTicksAxis(BaseAxis): 249 250 def __init__(self, dataRange, tickCount = 0, tickFilter = None, tickBase = 10, *args, **kwargs): 251 super(AutoTicksAxis, self).__init__(dataRange, *args, **kwargs) 252 self._tickCount = tickCount 253 self._tickFilter = tickFilter 254 self._ticks = [] 255 self._tickBase = tickBase 256 self._UpdateTicks() 257 258 def SetTickCount(self, tickCount): 259 self._tickCount = tickCount 260 self._OnChange() 261 262 def GetTicks(self): 263 return self._ticks 264 265 def _UpdateTicks(self): 266 if not self._tickCount: 267 self._ticks = [] 268 return 269 delta = (self._visibleRange[1] - self._visibleRange[0]) / self._tickCount 270 delta = self._tickBase ** math.ceil(math.log(delta, self._tickBase)) 271 start = math.floor(self._visibleRange[0] / delta) * delta 272 cnt = int((self._visibleRange[1] - self._visibleRange[0]) / delta) 273 while cnt * 2 < self._tickCount: 274 delta /= 2 275 cnt = int((self._visibleRange[1] - self._visibleRange[0]) / delta) 276 277 incr = 0 278 result = [] 279 while start + incr <= self._visibleRange[1]: 280 if not self._tickFilter or self._tickFilter(start + incr): 281 if not result or result[-1] != start + incr: 282 result.append(start + incr) 283 incr += delta 284 285 self._ticks = result 286 287 def _OnChange(self): 288 self._UpdateTicks() 289 super(AutoTicksAxis, self)._OnChange() 290 291 292 class AutoTicksCategoryAxis(CategoryAxis): 293 294 def __init__(self, dataRange, tickCount = 0, tickFilter = None, tickBase = 10, *args, **kwargs): 295 super(AutoTicksCategoryAxis, self).__init__(dataRange, *args, **kwargs) 296 self._tickCount = tickCount 297 self._tickFilter = tickFilter 298 self._tickBase = tickBase 299 self._ticks = [] 300 self._UpdateTicks() 301 302 def SetTickCount(self, tickCount): 303 self._tickCount = tickCount 304 self._OnChange() 305 306 def GetTicks(self): 307 return self._ticks 308 309 def _UpdateTicks(self): 310 if not self._tickCount: 311 self._ticks = [] 312 return 313 delta = (self._visibleRange[1] - self._visibleRange[0]) / self._tickCount 314 delta = self._tickBase ** math.ceil(math.log(delta, self._tickBase)) 315 a = math.floor(self._visibleRange[0] / delta) * delta 316 cnt = int((self._visibleRange[1] - self._visibleRange[0]) / delta) 317 while cnt * 2 < self._tickCount and delta > 1: 318 delta /= 2 319 cnt = int((self._visibleRange[1] - self._visibleRange[0]) / delta) 320 321 result = [] 322 while a <= self._visibleRange[1]: 323 if not self._tickFilter or self._tickFilter(a): 324 result.append(int(a)) 325 a += delta 326 327 self._ticks = result 328 329 def _OnChange(self): 330 self._UpdateTicks() 331 super(AutoTicksCategoryAxis, self)._OnChange() 332 333 334 class MonthlyTimeAxis(CategoryAxis): 335 336 def __init__(self, timestamps, *args, **kwargs): 337 super(MonthlyTimeAxis, self).__init__(timestamps, *args, **kwargs) 338 self._months = [GetStrippedLabel('/Carbon/UI/Common/Months/JanuaryShort'), 339 GetStrippedLabel('/Carbon/UI/Common/Months/FebruaryShort'), 340 GetStrippedLabel('/Carbon/UI/Common/Months/MarchShort'), 341 GetStrippedLabel('/Carbon/UI/Common/Months/AprilShort'), 342 GetStrippedLabel('/Carbon/UI/Common/Months/MayShort'), 343 GetStrippedLabel('/Carbon/UI/Common/Months/JuneShort'), 344 GetStrippedLabel('/Carbon/UI/Common/Months/JulyShort'), 345 GetStrippedLabel('/Carbon/UI/Common/Months/AugustShort'), 346 GetStrippedLabel('/Carbon/UI/Common/Months/SeptemberShort'), 347 GetStrippedLabel('/Carbon/UI/Common/Months/OctoberShort'), 348 GetStrippedLabel('/Carbon/UI/Common/Months/NovemberShort'), 349 GetStrippedLabel('/Carbon/UI/Common/Months/DecemberShort')] 350 351 def GetTicks(self): 352 months = [] 353 for i, t in enumerate(self._dataPoints): 354 if self._visibleRange[0] <= i <= self._visibleRange[1] and blue.os.GetTimeParts(t)[3] == 1: 355 months.append(i) 356 357 return months 358 359 def GetTickLabel(self, tickValue): 360 month = blue.os.GetTimeParts(self._dataPoints[tickValue])[1] 361 return self._months[month - 1] 362 363 def GetRangeText(self, start, end): 364 return str(int(round(end - start)))