NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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