Source code for ete4.smartview.renderer.draw_helpers

from collections import OrderedDict, namedtuple
from math import sin, cos, pi, sqrt, atan2

Box = namedtuple('Box', 'x y dx dy')  # corner and size of a 2D shape

Padding = namedtuple('Padding', 'x y')


[docs] def clip_angles(a1, a2): "Return the angles such that a1 to a2 extend at maximum from -pi to pi" EPSILON = 1e-8 # without it, p1 can be == p2 and svg arcs are not drawn return max(-pi + EPSILON, a1), min(pi - EPSILON, a2)
[docs] def cartesian(point): r, a = point return r * cos(a), r * sin(a)
[docs] def summary(nodes, prop="name"): "Return a list of names summarizing the given list of nodes" return list(OrderedDict((first_value(node, prop), None) for node in nodes).keys())
[docs] def first_value(tree, prop): "Return the value of the requested property for the first node that has it" return next((node.props.get(prop) for node in tree.traverse('preorder') if node.props.get(prop)), '')
[docs] def get_xs(box): x, _, dx, _ = box return x, x + dx
[docs] def get_ys(box): _, y, _, dy = box return y, y + dy
[docs] def intersects_box(b1, b2): "Return True if the boxes b1 and b2 (of the same kind) intersect" return (intersects_segment(get_xs(b1), get_xs(b2)) and intersects_segment(get_ys(b1), get_ys(b2)))
[docs] def intersects_segment(s1, s2): "Return True if the segments s1 and s2 intersect" s1min, s1max = s1 s2min, s2max = s2 return s1min <= s2max and s2min <= s1max
[docs] def intersects_angles(rect, asec): "Return True if any part of rect is contained within the angles of the asec" return any(intersects_segment(get_ys(circumasec(r)), get_ys(asec)) for r in split_thru_negative_xaxis(rect))
# We divide rect in two if it passes thru the -x axis, because then its # circumbscribing asec goes from -pi to +pi and (wrongly) always intersects.
[docs] def split_thru_negative_xaxis(rect): "Return a list of rectangles resulting from cutting the given one" x, y, dx, dy = rect if x >= 0 or y > 0 or y + dy < 0: return [rect] else: EPSILON = 1e-8 return [Box(x, y, dx, -y-EPSILON), Box(x, EPSILON, dx, dy + y)]
[docs] def circumrect(asec): "Return the rectangle that circumscribes the given annular sector" if asec is None: return None rmin, amin, dr, da = asec rmax, amax = rmin + dr, amin + da amin, amax = clip_angles(amin, amax) points = [(rmin, amin), (rmin, amax), (rmax, amin), (rmax, amax)] xs = [r * cos(a) for r,a in points] ys = [r * sin(a) for r,a in points] xmin, ymin = min(xs), min(ys) xmax, ymax = max(xs), max(ys) if amin < -pi/2 < amax: # asec traverses the -y axis ymin = -rmax if amin < 0 < amax: # asec traverses the +x axis xmax = rmax if amin < pi/2 < amax: # asec traverses the +y axis ymax = rmax # NOTE: the annular sectors we consider never traverse the -x axis. return Box(xmin, ymin, xmax - xmin, ymax - ymin)
[docs] def circumasec(rect): "Return the annular sector that circumscribes the given rectangle" if rect is None: return None x, y, dx, dy = rect points = [(x, y), (x, y+dy), (x+dx, y), (x+dx, y+dy)] radius2 = [x*x + y*y for x,y in points] if x <= 0 and x+dx >= 0 and y <= 0 and y+dy >= 0: return Box(0, -pi, sqrt(max(radius2)), 2*pi) else: angles = [atan2(y, x) for x,y in points] rmin, amin = sqrt(min(radius2)), min(angles) return Box(rmin, amin, sqrt(max(radius2)) - rmin, max(angles) - amin)
# Basic drawing elements.
[docs] def draw_nodebox(box, name='', properties=None, node_id=None, searched_by=None, style=None): properties = { k:v for k,v in (properties or {}).items() \ if not (k.startswith('_') or k == 'seq')} return ['nodebox', box, name, properties, node_id or [], searched_by or [], style or {}]
[docs] def draw_outline(box, style=None): return ['outline', box, style or {}]
[docs] def get_line_type(style): types = ['solid', 'dotted', 'dashed'] if style.get('type'): style['type'] = types[int(style['type'])] else: style['type'] = types[0] return style
[docs] def draw_line(p1, p2, line_type='', parent_of=None, style=None): style = get_line_type(style or {}) return ['line', p1, p2, line_type, parent_of or [], style]
[docs] def draw_arc(p1, p2, large=False, arc_type='', style=None): style = get_line_type(style or {}) return ['arc', p1, p2, int(large), arc_type, style]
[docs] def draw_circle(center, radius, circle_type='', style=None, tooltip=None): return ['circle', center, radius, circle_type, style or {}, tooltip or '']
[docs] def draw_ellipse(center, rx, ry, ellipse_type='', style=None, tooltip=None): return ['ellipse', center, rx, ry, ellipse_type, style or {}, tooltip or '']
[docs] def draw_slice(center, r, a, da, slice_type='', style=None, tooltip=None): return ['slice', (center, r, a, da), slice_type, style or {}, tooltip or '']
[docs] def draw_triangle(box, tip, triangle_type='', style=None, tooltip=None): """Returns array with all the information needed to draw a triangle in front end. :box: bounds triangle :tip: defines tip orientation 'top', 'left' or 'right'. :triangle_type: will label triangle in front end (class) """ return ['triangle', box, tip, triangle_type, style or {}, tooltip or '']
[docs] def draw_text(box, text, text_type='', rotation=0, anchor=None, style=None): return ['text', box, text, text_type, rotation, anchor or "", style or {}]
[docs] def draw_rect(box, rect_type, style=None, tooltip=None): return ['rect', box, rect_type, style or {}, tooltip or '']
[docs] def draw_rhombus(box, rhombus_type='', style=None, tooltip=None): """ Create rhombus provided a bounding box """ # Rotate the box to provide a rhombus (points) to drawing engine x, y, dx, dy = box rhombus = ((x + dx / 2, y), # top (x + dx, y + dy / 2), # right (x + dx / 2, y + dy), # bottom (x, y + dy / 2)) # left return ['rhombus', rhombus, rhombus_type, style or {}, tooltip or '']
[docs] def draw_arrow(box, tip, orientation='right', arrow_type='', style=None, tooltip=None): """ Create arrow provided a bounding box """ x, y, dx, dy = box if orientation == 'right': arrow = ((x, y), (x + dx - tip, y), (x + dx, y + dy / 2), (x + dx - tip, y + dy), (x, y + dy)) elif orientation == 'left': arrow = ((x, y + dy / 2), (x + tip, y), (x + dx, y), (x + dx, y + dy), (x + tip, y + dy)) return ['polygon', arrow, arrow_type, style or {}, tooltip or '']
[docs] def draw_array(box, a, tooltip=None): return ['array', box, a, tooltip or '']
[docs] def draw_html(box, html, html_type='', style=None): return ['html', box, html, html_type, style or {}]
[docs] def draw_img(box, img, img_type='', style=None): return ['img', box, img, img_type, style or {}]