donutChart.py (5574B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\control\donutChart.py 2 import math 3 import logging 4 from carbonui import uiconst 5 from carbonui.primitives.container import Container 6 from carbonui.uianimations import animations 7 from carbonui.util.color import Color 8 from eve.client.script.ui.control.donutSegment import DonutSegment 9 from signals import Signal 10 logger = logging.getLogger(__name__) 11 12 class SegmentData: 13 14 def __init__(self, value, segmentID = None, hint = None): 15 self.value = value 16 self.ratio = 0.0 17 self.segmentID = segmentID 18 self.hint = hint 19 20 21 class DonutChart(Container): 22 default_name = 'DonutChart' 23 default_radius = 50 24 default_startAngle = -math.pi / 2 25 default_isClockwise = True 26 default_lineWidth = 10.0 27 default_gapSize = 0.005 28 default_minSegmentSize = 0.02 29 default_color = (0.5, 0.65, 0.7, 1.0) 30 default_outputMode = uiconst.OUTPUT_COLOR 31 32 def ApplyAttributes(self, attributes): 33 Container.ApplyAttributes(self, attributes) 34 self.radius = attributes.get('radius', self.default_radius) 35 self.startAngle = attributes.get('startAngle', self.default_startAngle) 36 self.isClockwise = attributes.get('isClockwise', self.default_isClockwise) 37 self.gapSize = attributes.get('gapSize', self.default_gapSize) 38 self.minSegmentSize = attributes.get('minSegmentSize', self.default_minSegmentSize) 39 self.color = attributes.get('color', self.default_color) 40 self.lineWidth = attributes.get('lineWidth', self.default_lineWidth) 41 self.outputMode = attributes.get('outputMode', self.default_outputMode) 42 self.segmentDataByID = {} 43 self.segmentsByID = [] 44 self.width = self.height = self.radius * 2 45 self.on_segment_clicked = Signal(signalName='on_segment_clicked') 46 self.on_segment_mouse_enter = Signal(signalName='on_segment_mouse_enter') 47 self.on_segment_mouse_exit = Signal(signalName='on_segment_mouse_exit') 48 49 def AddSegment(self, value, segmentID, hint = None): 50 self.segmentDataByID[segmentID] = SegmentData(value, segmentID, hint) 51 52 def Reset(self): 53 self.segmentDataByID = {} 54 self.segmentsByID = {} 55 56 def SetValues(self, values, segmentIDs = None, animate = False, animDuration = 1.0): 57 self.Construct(animate, animDuration) 58 59 def Construct(self, animate = True, animDuration = 1.0): 60 self.Flush() 61 values = [ data.value for data in self.segmentDataByID.values() ] 62 if not sum(values) >= 0.9999: 63 logger.warning('not rendering donut chart because sum of values is too small: %s' % sum(values)) 64 return 65 self._UpdateSegmentRatios(values) 66 self.segmentsByID = self.ConstructSegments() 67 if animate: 68 self.AnimEntry(animDuration) 69 70 def AnimEntry(self, animDuration): 71 segments = sorted(self.segmentsByID.values(), key=lambda x: x.angle, reverse=True) 72 for i, segment in enumerate(segments): 73 self._AnimEntrySegment(animDuration, segment, i) 74 75 def ConstructSegments(self): 76 angle = 0.0 77 segments = {} 78 for data in self.GetSegmentData(): 79 color = self.GetSegmentColor(data.segmentID, data.value) 80 segment = DonutSegment(parent=self, segmentID=data.segmentID, radius=self.radius, startAngle=angle, isClockwise=self.isClockwise, angle=2 * math.pi * (data.ratio - self.gapSize), colorStart=color, colorEnd=color, lineWidth=self.lineWidth, hint=data.hint, outputMode=self.outputMode) 81 segment.on_clicked.connect(self.on_segment_clicked) 82 segment.on_mouse_enter.connect(self.on_segment_mouse_enter) 83 segment.on_mouse_exit.connect(self.on_segment_mouse_exit) 84 segments[data.segmentID] = segment 85 angle += data.ratio * 2 * math.pi 86 87 return segments 88 89 def _AnimEntrySegment(self, duration, segment, i): 90 timeOffset = i * 0.025 91 animations.FadeTo(segment, 0.0, 1.0, duration=duration, timeOffset=timeOffset) 92 if segment.angle < 0.1 * math.pi: 93 self._AnimEntrySmallSegment(duration * 0.5, segment, timeOffset) 94 else: 95 animations.MorphScalar(segment, 'end', 0.0, 1.0, duration=duration, timeOffset=timeOffset) 96 97 def _AnimEntrySmallSegment(self, duration, segment, timeOffset): 98 timeOffset += 0.3 * (1.0 - segment.angle / (0.5 * math.pi)) 99 angle = segment.startAngle + segment.angle / 2.0 100 r = 0.15 * segment.radius 101 x = r * math.cos(angle) 102 y = r * math.sin(angle) 103 animations.MorphScalar(segment, 'left', x, segment.left, duration=duration, timeOffset=timeOffset) 104 animations.MorphScalar(segment, 'top', y, segment.top, duration=duration, timeOffset=timeOffset) 105 106 def _UpdateSegmentRatios(self, values): 107 sumTotal = float(sum([ data.value for data in self.segmentDataByID.values() ])) 108 sumRatio = 0 109 for data in self.segmentDataByID.values(): 110 data.ratio = max(data.value / sumTotal, self.minSegmentSize) 111 sumRatio += data.ratio 112 113 for data in self.segmentDataByID.values(): 114 data.ratio /= sumRatio 115 116 def GetSegmentColor(self, segmentID, value): 117 data = self.segmentDataByID[segmentID] 118 x = 0.3 + 0.7 * data.ratio 119 return Color(*self.color).SetBrightness(x).GetRGBA() 120 121 def GetSegmentData(self): 122 segmentData = self.segmentDataByID.values() 123 return sorted(segmentData, key=lambda data: data.value)