def add_breadth_line_of_sibling_nodes(self, parent_id: tp.Tuple[str, int], start_coord: tp.Tuple[int, int], \ end_coord: tp.Tuple[int, int], \ node_specs: tp.List[tp.Optional[NodeSpec]] \ ) -> tp.List[int]: num_specs = len(node_specs) parent_id = self._id_if_str(parent_id) if num_specs < 2: raise ValueError("node_specs must have at least 2 elements") if node_specs[0] is None or node_specs[-1] is None: raise ValueError( "The first and last item of node_specs must not be None") added_ids = [] start_vec2 = angles.vec2(start_coord) end_vec2 = angles.vec2(end_coord) rel_vec2 = end_vec2 - start_vec2 count = 0 for spec in node_specs: if spec is not None: pos = start_vec2 + rel_vec2 * count / (num_specs - 1) new_id = self.add_node(spec.text, pos, spec.node_col, spec.multibox) if spec.link_draw == ArrowDraw.BACK_ARROW: self.add_link(new_id, parent_id, spec.link_col, ArrowDraw.FWD_ARROW, None) elif spec.link_draw != ArrowDraw.NO_LINK: self.add_link(parent_id, new_id, spec.link_col, spec.link_draw, spec.link_2_col) added_ids.append(new_id) count += 1 return added_ids
def _draw_arrowhead(self, surface, from_coord: tp.Tuple[int, int], to_coord: tp.Tuple[int, int], \ dry_run: bool=False, is_second_link: bool=False) -> bool: """ Calculates where to draw the arrowhead based on where the link would be visible between the two nodes, i.e. from the two intersection points. Returns False if, during the above calculation, it discovers the nodes are overlapping. That means the link would be entirely obscured. """ from_vec2 = angles.vec2(from_coord) to_vec2 = angles.vec2(to_coord) if not is_second_link: rel_vec_frac_from, intersection_from = \ self._from_node.get_intersection_point_to_link(from_vec2, to_vec2) rel_vec_frac_to, intersection_to = \ self._to_node.get_intersection_point_to_link(to_vec2, from_vec2) else: rel_vec_frac_from, intersection_from = \ self._to_node.get_intersection_point_to_link(to_vec2, from_vec2) rel_vec_frac_to, intersection_to = \ self._from_node.get_intersection_point_to_link(from_vec2, to_vec2) rel_vec_frac_to = 1 - rel_vec_frac_to if rel_vec_frac_to < rel_vec_frac_from: # The nodes have overlapped, the link would be completely obscured return False if dry_run or self._arrow_draw == ArrowDraw.NO_ARROW: return True rel_vec2 = intersection_to - intersection_from draw_arrow_at = intersection_from + (rel_vec2 * 2 / 3) left_endpoint, right_endpoint = self._get_arrow_endpoints( rel_vec2, draw_arrow_at) colour = self._second_colour if is_second_link \ else self._colour pygame.draw.line(surface, colour, draw_arrow_at, left_endpoint, self._width) pygame.draw.line(surface, colour, draw_arrow_at, right_endpoint, self._width) if not is_second_link and self._arrow_draw == ArrowDraw.DOUBLE_ARROW: draw_arrow_at = intersection_from + (rel_vec2 * 1 / 3) left_endpoint, right_endpoint = self._get_arrow_endpoints( -rel_vec2, draw_arrow_at) pygame.draw.line(surface, colour, draw_arrow_at, left_endpoint, self._width) pygame.draw.line(surface, colour, draw_arrow_at, right_endpoint, self._width) return True
def add_arc_of_sibling_nodes(self, parent_id: tp.Tuple[str, int], radius: int, start_dir_coord: tp.Tuple[int, int], \ end_dir_coord: tp.Tuple[int, int], clockwise: bool, \ node_specs: tp.List[tp.Optional[NodeSpec]] \ ) -> tp.List[int]: parent_id = self._id_if_str(parent_id) num_specs = len(node_specs) if num_specs < 2: raise ValueError("node_specs must have at least 2 elements") if node_specs[0] is None or node_specs[-1] is None: raise ValueError( "The first and last item of node_specs must not be None") added_ids = [] parent_pos = self._nodes[parent_id].pos parent_vec2 = angles.vec2(parent_pos) start_vec2 = angles.vec2(start_dir_coord) - parent_vec2 end_vec2 = angles.vec2(end_dir_coord) - parent_vec2 start_bear_rad = angles.get_bearing_rad_of(angles.flip_y(start_vec2)) end_bear_rad = angles.get_bearing_rad_of(angles.flip_y(end_vec2)) bear_diff_rad = angles.normalise_angle(end_bear_rad - start_bear_rad) if clockwise: bear_diff_rad = angles.flip_angle(bear_diff_rad) count = 0 for spec in node_specs: if spec is not None: rotate_anticlockwise_by = bear_diff_rad * count / (num_specs - 1) if clockwise: rotate_anticlockwise_by *= -1 dir_vec = angles.flip_y( \ angles.get_unit_vector_after_rotating( \ angles.flip_y(start_vec2), rotate_anticlockwise_by )) pos = parent_pos + dir_vec * radius new_id = self.add_node(spec.text, pos, spec.node_col, spec.multibox) if spec.link_draw == ArrowDraw.BACK_ARROW: self.add_link(new_id, parent_id, spec.link_col, ArrowDraw.FWD_ARROW, None) elif spec.link_draw != ArrowDraw.NO_LINK: self.add_link(parent_id, new_id, spec.link_col, spec.link_draw, spec.link_2_col) added_ids.append(new_id) count += 1 return added_ids
def add_depth_line_of_linked_nodes(self, start_id: tp.Tuple[str, int], dir: tp.Tuple[int, int], \ link_length: int, \ node_specs: tp.List[tp.Optional[NodeSpec]] \ ) -> tp.List[int]: added_ids = [] start_id = self._id_if_str(start_id) start_pos = angles.vec2(self._nodes[start_id].pos) unit_dir = angles.unit(dir) count = 1 from_id = start_id for spec in node_specs: if spec is not None: pos = start_pos + unit_dir * link_length * count new_id = self.add_node(spec.text, pos, spec.node_col, spec.multibox) if spec.link_draw == ArrowDraw.BACK_ARROW: self.add_link(new_id, from_id, spec.link_col, ArrowDraw.FWD_ARROW, None) elif spec.link_draw != ArrowDraw.NO_LINK: self.add_link(from_id, new_id, spec.link_col, spec.link_draw, spec.link_2_col) added_ids.append(new_id) from_id = new_id count += 1 return added_ids
def add_breadth_line_centered_on(self, parent_id: tp.Tuple[str, int], center_coord: tp.Tuple[int, int], \ link_length: int, node_specs: tp.List[tp.Optional[NodeSpec]] \ ) -> tp.List[int]: num_specs = len(node_specs) if num_specs < 2: raise ValueError("node_specs must have at least 2 elements") parent_pos = self.pos_of(parent_id) rel_vec2 = angles.vec2(center_coord) - parent_pos rotated_vec2 = angles.flip_y( \ angles.rotate_vector_to_left_by_90_deg( \ angles.flip_y( angles.unit(rel_vec2) ))) half_total_length = link_length * float(num_specs - 1) / 2.0 start_coord = center_coord + rotated_vec2 * half_total_length end_coord = center_coord - rotated_vec2 * half_total_length return self.add_breadth_line_of_sibling_nodes(parent_id, start_coord, end_coord, node_specs)