Coverage for /home/deng/Projects/ete4/hackathon/ete4/ete4/smartview/renderer/layouts/staple_layouts.py: 16%

102 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-03-21 09:19 +0100

1from ..treelayout import TreeLayout 

2from ..faces import TextFace, RectFace, ScaleFace 

3from ...utils import InvalidUsage 

4from ....utils import random_color 

5 

6 

7__all__ = [ "LayoutBarplot" ] 

8 

9 

10def interpolate_colors(c1, c2, mix=0): 

11 """Return a color between c1 (for mix=0) and c2 (for mix=1).""" 

12 # '#ff5500', '#001122', 0.5 -> '#7f3311' 

13 assert c1.startswith('#') and c2.startswith('#') and len(c1) == len(c2) == 7 

14 return '#' + ''.join('%02x' % int((1-mix) * int(c1[1+2*i:3+2*i], 16) + 

15 mix * int(c2[1+2*i:3+2*i], 16)) for i in range(3)) 

16 # NOTE: The original solution (depending on numpy and matplotlib -- not great), was: 

17 # https://stackoverflow.com/questions/25668828/how-to-create-colour-gradient-in-python 

18 

19 

20class LayoutPlot(TreeLayout): 

21 def __init__(self, name=None, width=200, size_prop=None, color_prop=None, 

22 position="aligned", column=0, 

23 color_gradient=None, color="red", colors=None, 

24 padding_x=10, scale=True, legend=True, active=True): 

25 super().__init__(name, 

26 aligned_faces=True if position == "aligned" else False, 

27 legend=legend, active=active) 

28 

29 self.width = width 

30 self.position = position 

31 self.column = column 

32 

33 self.scale = scale 

34 self.padding_x = padding_x 

35 

36 # if not (size_prop or color_prop): 

37 # raise InvalidUsage("Either size_prop or color_prop required") 

38 

39 self.size_prop = size_prop 

40 self.color_prop = color_prop 

41 

42 self.size_range = None 

43 self.color_range = None 

44 

45 self.color = color 

46 self.colors = colors 

47 self.color_gradient = color_gradient 

48 if self.color_prop and not self.color_gradient: 

49 self.color_gradient = ("#FFF", self.color) 

50 

51 def set_tree_style(self, tree, tree_style): 

52 super().set_tree_style(tree, tree_style) 

53 def update_vals(metric, node): 

54 p, minval, maxval, uniqvals = vals[metric] 

55 prop = node.props.get(p) 

56 try: 

57 prop = float(prop) 

58 vals[metric][1] = min(minval, prop) 

59 vals[metric][2] = max(maxval, prop) 

60 uniqvals.add(prop) 

61 except: 

62 return 

63 

64 vals = { 

65 "size": [ self.size_prop, 0, 0, set() ], # min, max, unique 

66 "color": [ self.color_prop, 0, 0, set() ] # min, max, unique 

67 } 

68 

69 for node in tree.traverse(): 

70 if self.size_prop: 

71 update_vals("size", node) 

72 

73 if self.color_prop: 

74 update_vals("color", node) 

75 

76 if self.size_prop: 

77 self.size_range = vals["size"][1:3] 

78 

79 if self.color_prop: 

80 unique = vals["color"][3] 

81 if len(unique): 

82 colors = self.colors or random_color(num=len(unique)) 

83 if type(colors) == dict: 

84 self.colors = colors.copy() 

85 else: 

86 colors = list(colors) 

87 self.colors = {} 

88 for idx, value in enumerate(unique): 

89 self.colors[value] = colors[idx % len(colors)] 

90 if self.legend: 

91 tree_style.add_legend(title=self.name, 

92 variable="discrete", 

93 colormap=self.colors) 

94 else: 

95 self.color_range = vals["color"][1:3] 

96 if self.legend: 

97 tree_style.add_legend(title=self.name, 

98 variable="continuous", 

99 value_range=self.color_range, 

100 color_range=self.color_gradient) 

101 

102 def get_size(self, node): 

103 if not self.size_prop: 

104 return self.width 

105 minval, maxval = self.size_range 

106 return float(node.props.get(self.size_prop, 0)) / float(maxval) * self.width 

107 

108 def get_color(self, node): 

109 if not self.color_prop: 

110 return self.color 

111 

112 prop = node.props.get(self.color_prop) 

113 if prop is None: 

114 return None 

115 

116 if self.color_range: 

117 minval, maxval = self.color_range 

118 mix = (prop - minval) / (maxval - minval) 

119 return interpolate_colors(*self.color_gradient, mix) 

120 else: 

121 return self.colors.get(prop) 

122 

123 def get_legend(self): 

124 return self.legend 

125 

126 

127class LayoutBarplot(LayoutPlot): 

128 def __init__(self, name=None, width=200, size_prop=None, 

129 color_prop=None, position="aligned", column=0, 

130 color_gradient=None, color="red", colors=None, 

131 padding_x=10, scale=True, legend=True, active=True): 

132 

133 name = name or f'Barplot_{size_prop}_{color_prop}' 

134 super().__init__(name=name, width=width, size_prop=size_prop, 

135 color_prop=color_prop, position=position, column=column, 

136 color_gradient=color_gradient, color=color, colors=colors, 

137 padding_x=padding_x, scale=scale, legend=legend, active=active) 

138 

139 def set_tree_style(self, tree, tree_style): 

140 super().set_tree_style(tree, tree_style) 

141 

142 if self.scale and self.size_range: 

143 scale = ScaleFace(width=self.width, scale_range=self.size_range, 

144 formatter='%.2f', 

145 padding_x=self.padding_x, padding_y=2) 

146 text = TextFace(self.name, max_fsize=11, padding_x=self.padding_x) 

147 tree_style.aligned_panel_header.add_face(scale, column=self.column) 

148 tree_style.aligned_panel_header.add_face(text, column=self.column) 

149 

150 def set_node_style(self, node): 

151 width = self.get_size(node) 

152 color = self.get_color(node) 

153 if width and color: 

154 tooltip = "" 

155 if node.name: 

156 tooltip += f'<b>{node.name}</b><br>' 

157 if self.size_prop: 

158 tooltip += f'<br>{self.size_prop}: {width}<br>' 

159 if self.color_prop: 

160 tooltip += f'<br>{self.color_prop}: {color}<br>' 

161 face = RectFace(width, None, color=color, 

162 tooltip=tooltip, padding_x=self.padding_x) 

163 node.add_face(face, position=self.position, column=self.column, 

164 collapsed_only=not node.is_leaf)