from ..treelayout import TreeLayout
from ..faces import TextFace, RectFace, ScaleFace
from ...utils import InvalidUsage
from ....utils import random_color
__all__ = [ "LayoutBarplot" ]
def interpolate_colors(c1, c2, mix=0):
"""Return a color between c1 (for mix=0) and c2 (for mix=1)."""
# '#ff5500', '#001122', 0.5 -> '#7f3311'
assert c1.startswith('#') and c2.startswith('#') and len(c1) == len(c2) == 7
return '#' + ''.join('%02x' % int((1-mix) * int(c1[1+2*i:3+2*i], 16) +
mix * int(c2[1+2*i:3+2*i], 16)) for i in range(3))
# NOTE: The original solution (depending on numpy and matplotlib -- not great), was:
# https://stackoverflow.com/questions/25668828/how-to-create-colour-gradient-in-python
class LayoutPlot(TreeLayout):
def __init__(self, name=None, width=200, size_prop=None, color_prop=None,
position="aligned", column=0,
color_gradient=None, color="red", colors=None,
padding_x=10, scale=True, legend=True, active=True):
super().__init__(name,
aligned_faces=True if position == "aligned" else False,
legend=legend, active=active)
self.width = width
self.position = position
self.column = column
self.scale = scale
self.padding_x = padding_x
# if not (size_prop or color_prop):
# raise InvalidUsage("Either size_prop or color_prop required")
self.size_prop = size_prop
self.color_prop = color_prop
self.size_range = None
self.color_range = None
self.color = color
self.colors = colors
self.color_gradient = color_gradient
if self.color_prop and not self.color_gradient:
self.color_gradient = ("#FFF", self.color)
def set_tree_style(self, tree, tree_style):
super().set_tree_style(tree, tree_style)
def update_vals(metric, node):
p, minval, maxval, uniqvals = vals[metric]
prop = node.props.get(p)
try:
prop = float(prop)
vals[metric][1] = min(minval, prop)
vals[metric][2] = max(maxval, prop)
uniqvals.add(prop)
except:
return
vals = {
"size": [ self.size_prop, 0, 0, set() ], # min, max, unique
"color": [ self.color_prop, 0, 0, set() ] # min, max, unique
}
for node in tree.traverse():
if self.size_prop:
update_vals("size", node)
if self.color_prop:
update_vals("color", node)
if self.size_prop:
self.size_range = vals["size"][1:3]
if self.color_prop:
unique = vals["color"][3]
if len(unique):
colors = self.colors or random_color(num=len(unique))
if type(colors) == dict:
self.colors = colors.copy()
else:
colors = list(colors)
self.colors = {}
for idx, value in enumerate(unique):
self.colors[value] = colors[idx % len(colors)]
if self.legend:
tree_style.add_legend(title=self.name,
variable="discrete",
colormap=self.colors)
else:
self.color_range = vals["color"][1:3]
if self.legend:
tree_style.add_legend(title=self.name,
variable="continuous",
value_range=self.color_range,
color_range=self.color_gradient)
def get_size(self, node):
if not self.size_prop:
return self.width
minval, maxval = self.size_range
return float(node.props.get(self.size_prop, 0)) / float(maxval) * self.width
def get_color(self, node):
if not self.color_prop:
return self.color
prop = node.props.get(self.color_prop)
if prop is None:
return None
if self.color_range:
minval, maxval = self.color_range
mix = (prop - minval) / (maxval - minval)
return interpolate_colors(*self.color_gradient, mix)
else:
return self.colors.get(prop)
def get_legend(self):
return self.legend
[docs]
class LayoutBarplot(LayoutPlot):
[docs]
def __init__(self, name=None, width=200, size_prop=None,
color_prop=None, position="aligned", column=0,
color_gradient=None, color="red", colors=None,
padding_x=10, scale=True, legend=True, active=True):
name = name or f'Barplot_{size_prop}_{color_prop}'
super().__init__(name=name, width=width, size_prop=size_prop,
color_prop=color_prop, position=position, column=column,
color_gradient=color_gradient, color=color, colors=colors,
padding_x=padding_x, scale=scale, legend=legend, active=active)
[docs]
def set_tree_style(self, tree, tree_style):
super().set_tree_style(tree, tree_style)
if self.scale and self.size_range:
scale = ScaleFace(width=self.width, scale_range=self.size_range,
formatter='%.2f',
padding_x=self.padding_x, padding_y=2)
text = TextFace(self.name, max_fsize=11, padding_x=self.padding_x)
tree_style.aligned_panel_header.add_face(scale, column=self.column)
tree_style.aligned_panel_header.add_face(text, column=self.column)
[docs]
def set_node_style(self, node):
width = self.get_size(node)
color = self.get_color(node)
if width and color:
tooltip = ""
if node.name:
tooltip += f'<b>{node.name}</b><br>'
if self.size_prop:
tooltip += f'<br>{self.size_prop}: {width}<br>'
if self.color_prop:
tooltip += f'<br>{self.color_prop}: {color}<br>'
face = RectFace(width, None, color=color,
tooltip=tooltip, padding_x=self.padding_x)
node.add_face(face, position=self.position, column=self.column,
collapsed_only=not node.is_leaf)