ledgerGraph.py (18059B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\ledger\ledgerGraph.py 2 from carbonui.primitives.frame import Frame 3 from collections import defaultdict, OrderedDict 4 import evetypes 5 from caching.memoize import Memoize 6 from carbon.common.script.util.format import FmtAmt, GetTimeParts, FmtDate 7 from carbonui import TextColor 8 from carbonui.graphs.axislabels import AxisLabels 9 from carbonui.graphs.graph import GraphArea 10 from carbonui.graphs.bargraph import BarGraph, DynamicHint 11 from carbonui.graphs.linegraph import LineGraph 12 from carbonui.graphs.pointgraph import PointGraph 13 from carbonui.graphs.pool import Pool 14 from carbonui.primitives.container import Container 15 import carbonui.graphs.axis as axis 16 import carbonui.const as uiconst 17 from carbonui.primitives.containerAutoSize import ContainerAutoSize 18 from carbonui.primitives.fill import Fill 19 from eve.client.script.ui.control.eveIcon import Icon 20 from eve.client.script.ui.control.eveLabel import EveLabelSmall, EveCaptionSmall, EveLabelMedium 21 from eve.client.script.ui.control.utilMenu import UtilMenu 22 from eve.client.script.ui.shared.ledger.ledgerUtil import IsInRange, GetMaxDayTimeStamp, GetColorForBaseTypeID, GetColorsForTypeIDs, REST_ORE, GetMetaLevelByTypeID 23 from eve.common.script.util.eveFormat import FmtISK 24 from eveservices.menu import GetMenuService 25 from inventorycommon.typeHelpers import GetAveragePrice 26 from localization import GetByLabel 27 MAX_LINES = 15 28 MAX_BARS = 15 29 GRAPH_DISPLAY_UNITS_QTY = 1 30 GRAPH_DISPLAY_UNITS_VOLUME = 2 31 GRAPH_DISPLAY_UNITS_PRICE = 3 32 DEFAULT_DISPLAY_UNIT = GRAPH_DISPLAY_UNITS_QTY 33 SETTING_NAME_BARGRAPH = 'ledger_barGraph_displayUnits' 34 SETTING_NAME_POINTGRAPH = 'ledger_pointGraph_displayUnits' 35 DISPLAY_UNIT_TEXTS = {GRAPH_DISPLAY_UNITS_QTY: 'UI/Ledger/DisplayUnitQty', 36 GRAPH_DISPLAY_UNITS_VOLUME: 'UI/Ledger/DisplayUnitVolume', 37 GRAPH_DISPLAY_UNITS_PRICE: 'UI/Ledger/DisplayUnitPrice'} 38 39 class LedgerGraphCont(Container): 40 __notifyevents__ = ['OnLedgerMouseOverChanged'] 41 42 def ApplyAttributes(self, attributes): 43 Container.ApplyAttributes(self, attributes) 44 self.barGraph = None 45 self.lineAndPointGraphsByTypeID = {} 46 self.currentLineHilite = None 47 self.leftCont = Container(name='leftCont', parent=self, align=uiconst.TOLEFT_PROP, width=0.5) 48 self.leftInnerCont = Container(name='leftInnerCont', parent=self.leftCont, align=uiconst.TOALL) 49 self.rightCont = Container(name='rightCont', parent=self, align=uiconst.TORIGHT_PROP, width=0.5) 50 self.rightInnerCont = Container(name='rightInnerCont', parent=self.rightCont, align=uiconst.TOALL) 51 self.AddLabelsAndSettings() 52 sm.RegisterNotify(self) 53 54 def AddLabelsAndSettings(self): 55 self.ConstructLeftContHeader() 56 self.UpdateTotalOreLabel() 57 self.ConstructRightContHeader() 58 self.UpdateOreByDayLabel() 59 60 def ConstructLeftContHeader(self): 61 topCont = ContainerAutoSize(name='topCont', align=uiconst.TOTOP, parent=self.leftCont, idx=0, padTop=10) 62 self.totalOreLabel = EveCaptionSmall(parent=topCont, text='', align=uiconst.CENTERLEFT, left=50) 63 UtilMenu(parent=topCont, align=uiconst.CENTERLEFT, GetUtilMenu=self.SettingsMenuForBarGraph, texturePath='res:/UI/Texture/SettingsCogWheel.png', opacity=TextColor.NORMAL.opacity, left=25) 64 65 def ConstructRightContHeader(self): 66 topCont = ContainerAutoSize(name='topCont', align=uiconst.TOTOP, parent=self.rightCont, idx=0, padTop=10) 67 self.oreByDayLabel = EveCaptionSmall(parent=topCont, align=uiconst.CENTERLEFT, left=50) 68 UtilMenu(parent=topCont, align=uiconst.CENTERLEFT, GetUtilMenu=self.SettingsMenuForPointGraph, texturePath='res:/UI/Texture/SettingsCogWheel.png', opacity=TextColor.NORMAL.opacity, left=25) 69 70 def UpdateOreByDayLabel(self): 71 pointGraphDisplayUnits = GetSetting(SETTING_NAME_POINTGRAPH, DEFAULT_DISPLAY_UNIT) 72 pointGraphLabel = DISPLAY_UNIT_TEXTS.get(pointGraphDisplayUnits, None) 73 pointGraphLabel = ' (%s)' % GetByLabel(pointGraphLabel) if pointGraphLabel else '' 74 self.oreByDayLabel.text = GetByLabel('UI/Ledger/GraphHeaderOreByDay') + pointGraphLabel 75 76 def UpdateTotalOreLabel(self): 77 barGraphDisplayUnits = GetSetting(SETTING_NAME_BARGRAPH, DEFAULT_DISPLAY_UNIT) 78 barGraphLabel = DISPLAY_UNIT_TEXTS.get(barGraphDisplayUnits, None) 79 barGraphLabel = ' (%s)' % GetByLabel(barGraphLabel) if barGraphLabel else '' 80 self.totalOreLabel.text = GetByLabel('UI/Ledger/GraphHeaderTotalOre') + barGraphLabel 81 82 def SettingsMenuForBarGraph(self, menuParent): 83 return self.SettingsMenuForGraph(menuParent, SETTING_NAME_BARGRAPH) 84 85 def SettingsMenuForPointGraph(self, menuParent): 86 return self.SettingsMenuForGraph(menuParent, SETTING_NAME_POINTGRAPH) 87 88 def SettingsMenuForGraph(self, menuParent, settingName): 89 for settingValue, labelPath in ((GRAPH_DISPLAY_UNITS_QTY, 'UI/Ledger/DisplayUnitQty'), (GRAPH_DISPLAY_UNITS_VOLUME, 'UI/Ledger/DisplayUnitVolume'), (GRAPH_DISPLAY_UNITS_PRICE, 'UI/Ledger/DisplayUnitPrice')): 90 isChecked = GetSetting(settingName, DEFAULT_DISPLAY_UNIT) == settingValue 91 menuParent.AddRadioButton(text=GetByLabel(labelPath), checked=isChecked, callback=(self.OnSettingChanged, settingName, settingValue)) 92 93 def OnSettingChanged(self, settingName, configName): 94 SetSetting(settingName, configName) 95 sm.ScatterEvent('OnMiningLedgerGraphSettingChanged') 96 self.UpdateTotalOreLabel() 97 self.UpdateOreByDayLabel() 98 99 def UpdateGraphFromRawData(self, rawData, start, stop): 100 self.leftInnerCont.Flush() 101 self.rightInnerCont.Flush() 102 displayUnits = GetSetting(SETTING_NAME_BARGRAPH, DEFAULT_DISPLAY_UNIT) 103 oreByTypeIDOrdered = GetGroupDataToOreType(rawData, start, stop, MAX_BARS, displayUnits) 104 self.LoadBarGraph(oreByTypeIDOrdered, displayUnits) 105 self.LoadPointGraph(rawData, start, stop) 106 107 def LoadBarGraph(self, oreByTypeIDOrdered, displayUnits): 108 data = [ v for v in oreByTypeIDOrdered.itervalues() ] 109 if not oreByTypeIDOrdered: 110 EveLabelMedium(parent=self.leftInnerCont, text='<center>%s</center>' % GetByLabel('UI/Ledger/NoDataToDisplay'), idx=0, align=uiconst.TOTOP, top=20) 111 return 112 yMin, yMax, xNum = self.GetGraphRanges(data) 113 graphArea, horizontalAxis, verticalAxis = self.GetCommonGraphElements(self.leftInnerCont, yMin, yMax, oreByTypeIDOrdered.items(), True) 114 indexByTypeID = {typeID:i for i, typeID in enumerate(oreByTypeIDOrdered.keys())} 115 116 def getHint(barIdx): 117 typeID = oreByTypeIDOrdered.keys()[barIdx] 118 value = data[barIdx] 119 return GetTextForHint(typeID, value, displayUnits) 120 121 colors = GetColorsForTypeIDs(oreByTypeIDOrdered.keys()) 122 self.barGraph = BarGraph(parent=graphArea, categoryAxis=horizontalAxis, valueAxis=verticalAxis, values=data, color=(0.9, 0.7, 0.3, 0.7), lineWidth=1, hint=DynamicHint(getHint), barSize=20, barColors=colors, barSizeMinMax=(10, 60)) 123 self.barGraph.indexByTypeID = indexByTypeID 124 125 def GetGraphRanges(self, data): 126 _, yMax = axis.GetRangeFromSequences(data) 127 yMin = 0 128 xNum = len(data) 129 return (yMin, yMax, xNum) 130 131 def GetCommonGraphElements(self, parent, yMin, yMax, data, showHorizontalAxis = False): 132 main = Container(name='mainGraphContainer', parent=parent, align=uiconst.TOALL) 133 verticalAxis = axis.AutoTicksAxis((yMin, yMax), tickCount=5, margins=(0.1, 0.1), behavior=axis.AXIS_FROM_ZERO) 134 verticalAxis.GetTickLabel = self.GetTickLabel 135 horizontalAxis = axis.CategoryAxis(data, labelFormat=int) 136 xAxisHeight = 32 137 AxisLabels(parent=main, align=uiconst.TOLEFT, width=50, axis=verticalAxis, orientation=axis.AxisOrientation.VERTICAL, minFactor=1.0, maxFactor=0.0, padBottom=xAxisHeight) 138 axisCont = Container(parent=main, align=uiconst.TOBOTTOM, height=xAxisHeight) 139 if showHorizontalAxis: 140 AxisIcons(parent=axisCont, align=uiconst.TOBOTTOM, height=xAxisHeight, axis=horizontalAxis, orientation=axis.AxisOrientation.HORIZONTAL, minFactor=0.0, maxFactor=1.0) 141 graphArea = GraphArea(name='graph', parent=main, align=uiconst.TOALL, clipChildren=False) 142 Fill(bgParent=graphArea, padBottom=-4, color=(0.0, 0.0, 0.0, 0.3)) 143 Frame(bgParent=graphArea, padBottom=-4, color=(0.5, 0.5, 0.5, 0.2)) 144 graphArea.minLabel = EveLabelSmall(parent=graphArea, align=uiconst.BOTTOMLEFT, top=-18) 145 graphArea.maxLabel = EveLabelSmall(parent=graphArea, align=uiconst.BOTTOMRIGHT, top=-18) 146 graphArea.AddAxis(axis.AxisOrientation.VERTICAL, verticalAxis, 1.0, 0.0) 147 graphArea.AddAxis(axis.AxisOrientation.HORIZONTAL, horizontalAxis) 148 return (graphArea, horizontalAxis, verticalAxis) 149 150 def LoadPointGraph(self, rawData, startTimestamp, endTimeStamp): 151 endTimeStamp = GetMaxDayTimeStamp(endTimeStamp) 152 dataByTypeID = defaultdict(list) 153 for eachData in rawData: 154 dataByTypeID[eachData.typeID].append(eachData) 155 156 graphArea = None 157 self.lineAndPointGraphsByTypeID.clear() 158 displayUnits = GetSetting(SETTING_NAME_POINTGRAPH, DEFAULT_DISPLAY_UNIT) 159 topOreTypes = GetGroupDataToOreType(rawData, startTimestamp, endTimeStamp, MAX_LINES, displayUnits) 160 161 def HintFunc(data, oreTypeID): 162 163 def getHint(barIdx): 164 value = data[barIdx] 165 return GetTextForHint(oreTypeID, value, displayUnits) 166 167 return getHint 168 169 maxYValue = 0 170 qtyByDayOrderedByOreTypeID = defaultdict(dict) 171 for oreTypeID in topOreTypes: 172 if oreTypeID == REST_ORE: 173 continue 174 dataForType = dataByTypeID.get(oreTypeID, []) 175 qtyByDayOrdered = GetNumByDay(dataForType, startTimestamp, endTimeStamp, displayUnits) 176 qtyByDayOrderedByOreTypeID[oreTypeID] = qtyByDayOrdered 177 maxYValue = max(maxYValue, max((x for x in qtyByDayOrdered.itervalues()))) 178 179 dataToMakeLineGraph = [] 180 for oreTypeID in topOreTypes: 181 qtyByDayOrdered = qtyByDayOrderedByOreTypeID.get(oreTypeID) 182 if not qtyByDayOrdered: 183 continue 184 data = qtyByDayOrdered.values() 185 if graphArea is None: 186 yMin, yMax, xNum = self.GetGraphRanges(data) 187 graphArea, horizontalAxis, verticalAxis = self.GetCommonGraphElements(self.rightInnerCont, 0, maxYValue, range(0, xNum)) 188 color = GetColorForBaseTypeID(oreTypeID) 189 pointGraph = PointGraph(parent=graphArea, categoryAxis=horizontalAxis, valueAxis=verticalAxis, values=data, pointColor=color, pointSize=4, hint=DynamicHint(HintFunc(data, oreTypeID)), hideZero=True) 190 dataForLineGraph = (oreTypeID, 191 data, 192 color, 193 pointGraph) 194 dataToMakeLineGraph.append(dataForLineGraph) 195 196 for eachOreTypeID, eachData, eachColor, eachPointGraph in dataToMakeLineGraph: 197 lineGraph = LineGraph(parent=graphArea, categoryAxis=horizontalAxis, valueAxis=verticalAxis, values=eachData, color=eachColor, lineWidth=1, state=uiconst.UI_NORMAL) 198 lineGraph.OnMouseEnter = (self.OnLineGraphEnter, lineGraph, eachPointGraph) 199 lineGraph.OnMouseExit = (self.OnLineGraphExit, lineGraph, eachPointGraph) 200 eachPointGraph.OnMouseEnter = (self.OnPointGraphEnter, lineGraph, eachPointGraph) 201 eachPointGraph.OnMouseExit = (self.OnPointGraphExit, lineGraph, eachPointGraph) 202 lineGraph.hint = evetypes.GetName(eachOreTypeID) 203 lineGraph.GetTooltipPosition = lambda *args: self.GetTooltipPosition() 204 self.lineAndPointGraphsByTypeID[eachOreTypeID] = (lineGraph, eachPointGraph) 205 206 if graphArea: 207 graphArea.minLabel.text = FmtDate(startTimestamp, 'ln') 208 graphArea.maxLabel.text = FmtDate(endTimeStamp, 'ln') 209 else: 210 EveLabelMedium(parent=self.rightInnerCont, text='<center>%s</center>' % GetByLabel('UI/Ledger/NoDataToDisplay'), idx=0, align=uiconst.TOTOP, top=20) 211 212 def GetTooltipPosition(self, *args): 213 from carbonui.uicore import uicore 214 return (uicore.uilib.x - 5, 215 uicore.uilib.y - 5, 216 10, 217 10) 218 219 def OnLineGraphEnter(self, lineGraph, pointGraph, *args): 220 LineGraph.OnMouseEnter(lineGraph, *args) 221 self.ChangeLineOpacity(lineGraph, pointGraph, highlighted=True) 222 223 def OnLineGraphExit(self, lineGraph, pointGraph, *args): 224 LineGraph.OnMouseExit(lineGraph, *args) 225 self.ChangeLineOpacity(lineGraph, pointGraph, highlighted=False) 226 227 def OnPointGraphEnter(self, lineGraph, pointGraph, *args): 228 PointGraph.OnMouseEnter(pointGraph, *args) 229 self.ChangeLineOpacity(lineGraph, pointGraph, highlighted=True) 230 231 def OnPointGraphExit(self, lineGraph, pointGraph, *args): 232 PointGraph.OnMouseExit(pointGraph, *args) 233 self.ChangeLineOpacity(lineGraph, pointGraph, highlighted=False) 234 235 def ChangeLineOpacity(self, lineGraph, pointGraph, highlighted = False): 236 if highlighted: 237 opacity = 2.0 238 else: 239 opacity = 1.0 240 lineGraph.opacity = opacity 241 pointGraph.opacity = opacity 242 243 def OnLedgerMouseOverChanged(self, typeID): 244 if self.barGraph: 245 barIdx = self.barGraph.indexByTypeID.get(typeID) 246 if barIdx is None: 247 self.barGraph._RemoveBarHighlight() 248 else: 249 self.barGraph.AddBarHighlight(barIdx) 250 if typeID: 251 typeIDToChange = typeID 252 doHilite = True 253 else: 254 typeIDToChange = self.currentLineHilite 255 doHilite = False 256 if typeIDToChange: 257 lineGraph, pointGraph = self.lineAndPointGraphsByTypeID.get(typeIDToChange, (None, None)) 258 if lineGraph and pointGraph: 259 self.ChangeLineOpacity(lineGraph, pointGraph, doHilite) 260 self.currentLineHilite = typeID 261 262 def GetTickLabel(self, tick): 263 if tick < 1000: 264 return int(tick) 265 return FmtAmt(tick, fmt='sn', showFraction=0) 266 267 268 def GetNumByDay(allData, startTimestamp, endTimeStamp, graphType = DEFAULT_DISPLAY_UNIT): 269 qtyByDay = defaultdict(int) 270 for eachData in allData: 271 year, month, wd, day, hour, minutes, sec, ms = GetTimeParts(eachData.eventDate) 272 dataKey = (year, month, day) 273 qtyByDay[dataKey] += GetNumToTrack(eachData, graphType) 274 275 dataInOrder = OrderedDict() 276 currentTimestamp = startTimestamp 277 counter = 0 278 while currentTimestamp < endTimeStamp and counter < 1000: 279 year, month, wd, day, hour, minutes, sec, ms = GetTimeParts(currentTimestamp) 280 dataKey = (year, month, day) 281 value = qtyByDay.get(dataKey, 0) 282 dataInOrder[dataKey] = value 283 currentTimestamp += const.DAY 284 counter += 1 285 286 return dataInOrder 287 288 289 def GetGroupDataToOreType(allData, start = None, end = None, numToGet = None, graphType = DEFAULT_DISPLAY_UNIT): 290 qtyByOreTypeID = defaultdict(int) 291 for eachData in allData: 292 isInRange = IsInRange(eachData.eventDate, start, end) 293 if not isInRange: 294 continue 295 qtyByOreTypeID[eachData.typeID] += GetNumToTrack(eachData, graphType) 296 297 qtyAndOreTypeIDList = qtyByOreTypeID.items() 298 qtyAndOreTypeIDList.sort(key=lambda x: (x[1], -GetMetaLevelByTypeID(x[0])), reverse=True) 299 if numToGet: 300 topOre = qtyAndOreTypeIDList[:numToGet] 301 rest = qtyAndOreTypeIDList[numToGet:] 302 restQty = sum((x[1] for x in rest)) 303 else: 304 topOre = qtyAndOreTypeIDList 305 restQty = 0 306 oreByTypeIDOrdered = OrderedDict(topOre) 307 if restQty: 308 oreByTypeIDOrdered[REST_ORE] = restQty 309 return oreByTypeIDOrdered 310 311 312 def GetNumToTrack(eachData, graphType): 313 if graphType == GRAPH_DISPLAY_UNITS_QTY: 314 return eachData.quantity 315 if graphType == GRAPH_DISPLAY_UNITS_VOLUME: 316 return eachData.quantity * GetTypeVolume(eachData.typeID) 317 if graphType == GRAPH_DISPLAY_UNITS_PRICE: 318 return eachData.quantity * GetPrice(eachData.typeID) 319 return 0 320 321 322 def GetTextForHint(typeID, value, displayUnits): 323 if typeID == REST_ORE: 324 oreName = GetByLabel('UI/Ledger/OtherOre') 325 else: 326 oreName = evetypes.GetName(typeID) 327 if displayUnits == GRAPH_DISPLAY_UNITS_QTY: 328 fmtValue = FmtAmt(value) 329 if displayUnits == GRAPH_DISPLAY_UNITS_VOLUME: 330 fmtValue = GetByLabel('UI/Moonmining/NumM3', amount=FmtAmt(value)) 331 if displayUnits == GRAPH_DISPLAY_UNITS_PRICE: 332 fmtValue = FmtISK(value, showFractionsAlways=0) 333 return '%s<br>%s' % (oreName, fmtValue) 334 335 336 @Memoize(1) 337 def GetTypeVolume(typeID): 338 return evetypes.GetVolume(typeID) 339 340 341 @Memoize(1) 342 def GetPrice(typeID): 343 price = GetAveragePrice(typeID) or evetypes.GetBasePrice(typeID) or 0 344 return price 345 346 347 def SetSetting(settingName, configName): 348 settings.user.ui.Set(settingName, configName) 349 350 351 def GetSetting(settingName, defaultValue): 352 return settings.user.ui.Get(settingName, defaultValue) 353 354 355 class AxisIcons(AxisLabels): 356 357 def CreatePools(self): 358 self._label_pool = Pool(Icon) 359 self._container_pool = Pool(Container) 360 361 def AddLabel(self, cont, tick): 362 cont.padding = (0, 0, 0, 0) 363 icon = self._label_pool.get_instance() 364 typeID, value = self._axis.GetDataPoints()[tick] 365 if typeID > -1: 366 cont.state = uiconst.UI_NORMAL 367 icon.SetParent(cont) 368 icon.state = uiconst.UI_NORMAL 369 icon.SetAlign(uiconst.CENTERTOP) 370 icon.SetSize(32, 32) 371 icon.LoadIconByTypeID(typeID=typeID) 372 icon.GetHint = lambda *args: GetTextForHint(typeID, value, GetSetting(SETTING_NAME_BARGRAPH, DEFAULT_DISPLAY_UNIT)) 373 icon.GetMenu = lambda *args: GetMenuService().GetMenuFromItemIDTypeID(None, typeID=typeID) 374 return icon