Skip to content

Instantly share code, notes, and snippets.

@abul4fia
Created February 16, 2024 12:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abul4fia/fc74f5d6b0ef8243003c98c9c4c867bd to your computer and use it in GitHub Desktop.
Save abul4fia/fc74f5d6b0ef8243003c98c9c4c867bd to your computer and use it in GitHub Desktop.

OrthogonalConnection

The class OrthogonalConnection creates a polyline that goes from p1 to p2, composed only by horizontal and vertical segments. The shape of the line is controlled by the path parameter.

There are two ways to use this class:

  1. p1 and p2 are the start and end points of the line, and path is a string with the characters "-" and "|" indicating the horizontal and vertical segments of the line.

    For example "-|-" will start with a horizontal segment, that will go until the x coordinate intermediate between p1 and p2, then a vertical segment up to the y coordinate of p2, and finally a horizontal segment to p2.

    If several "-" or "|" are used, each one represent a fraction of the total distance to be covered. For example "--|-" will make the vertical segment to be a 2/3 in horizontal distance from p1 to p2, because "--|-" contains 3 "-" segments.

  2. p2 is omitted and path is a list of tuples [(p, s), ... ] where p is a point and s is a string with the characters "-" and "|" as in the previous case. In this case a line is created from p1 to p, following the path in s, and the procedure is repeated for the next tuple in the list, until the list is exhausted. The final point of the polyline is the last p in the list.

The class returns a Line object that contains several segments. You can add a tip to it (see example), and use it as any other manim object.

In addition, that line provides a method .midpoint() which returns the point at proportion 0.5 of the whole path, and accepts an optional integer parameter segment. If segment is provided, it identifies one of the segments of the polyline (there is one segment for each "-" or "|" in the path), and returns the midpoint of that segment. This is useful to put labels (see example).

Example

The image resulting from exmaple.py is the following:

image

from orthogonal_connection import OrthogonalConnection
class Test(Scene):
def construct(self):
# Two rectangles to be connected
r1 = Rectangle(height=6, width=3)
r2 = Rectangle(height=4.5, width=2)
VGroup(r1, r2).arrange(RIGHT, buff=3)
self.add(r1, r2)
# Define some anchor and reference points
r1_right = r1.get_right()
r1_bot = r1.get_bottom()
r1_right2 = r1.point_from_proportion(0.8)
r2_top = r2.get_top()
r2_top_off = r2_top + UP
r2_right = r2.get_right()
r2_corner_off = r2.get_critical_point(DR) + DR
r2_left2 = r2.point_from_proportion(0.45)
# Example using path=[(p,s), ...] syntax
l1 = OrthogonalConnection(r1_right,
path=[(r2_top_off, "-|-"),
(r2_top, "|")],
color=RED
)
# To add a tip, use this trick instead of
# l1.add_tip(), because .add_tip() adjusts the
# curve and would tilt the polyline
l1.add(l1.create_tip())
txt1 = (Text("-|-, then |", color=RED).scale(.5)
.next_to(l1.midpoint(1), LEFT))
# Another example using path=[(p,s), ...] syntax
l2 = OrthogonalConnection(r1_bot,
path=[(r2_corner_off, "|-"),
(r2_right, "|-")],
color=BLUE
)
l2.add(l2.create_tip())
txt2 = (Text("|-, then |-", color=BLUE).scale(.5)
.next_to(l2.midpoint(1), UP))
# Example using path=str syntax (requires p1 and p2)
l3 = OrthogonalConnection(r1_right2,
r2_left2,
path="-|-",
color=GREEN)
l3.add(l3.create_tip())
txt3 = (Text("-|-", color=GREEN).scale(.5)
.next_to(l3.midpoint(0), DOWN))
# Add the three lines to the image
self.add(l1, l2, l3, txt1, txt2, txt3)
from manim import *
class OrthogonalConnection(Line):
"""Creates a polyline that goes from p1 to p2, composed
only by horizontal and vertical segments. The shape of the
line is controlled by the path parameter.
There are two ways to use this class:
1. p1 and p2 are the start and end points of the line, and
path is a string with the characters "-" and "|" indicating
the horizontal and vertical segments of the line.
For example "-|-" will start with a horizontal segment, that
will go until the x coordinate intermediate between p1 and p2,
then a vertical segment up to the y coordinate of p2, and finally
a horizontal segment to p2.
If several "-" or "|" are used, each one represent a
fraction of the total distance to be covered. For example "--|-"
will make the vertical segment to be a 2/3 in horizontal
distance from p1 to p2, because "--|-" contains 3 "-" segments.
2. p2 is omitted and path is a list of tuples (p, s) where p is
a point and s is a string with the characters "-" and "|" as
in the previous case. In this case a line is created from p1
to p, following the path in s, and the procedure is repeated
for the next tuple in the list, until the list is exhausted.
The final point of the polyline is the last p in the list.
The class returns a Line object that contains several segments
"""
def __init__(self, p1, p2=None, path=None, **kwargs):
if p2 is None:
if path is None:
p2 = p1
path = [(p2, "-|")]
elif type(path) == str:
p2 = p1
path = [(p2, path)]
else:
p2 = path[-1][0]
if type(path) == str:
path = [(p2, path)]
super().__init__(p1, p2, **kwargs)
self.add_corners(path)
def add_corners(self, path):
last_visited = self.get_start()
vertices = [last_visited]
for dst, p in path:
num_horiz = p.count("-")
num_vert = p.count("|")
start = last_visited
end = dst
x_length = end[0] - start[0]
y_length = end[1] - start[1]
for c in p:
vertex = vertices[-1].copy()
if c == "-":
vertex[0] += x_length/num_horiz
elif c == "|":
vertex[1] += y_length/num_vert
vertices.append(vertex)
last_visited = dst
self.set_points_as_corners(vertices)
def midpoint(self, segment=-1):
"""Returns a point on the polyline. If segment is given, it
returns the midpoint of the segment (numbered from 0),
otherwise it returns the midpoint of the whole polyline.
Each segment corresponds to a "-" or "|" in the path string.
"""
if segment >= 0:
vertices = self.get_anchors()
if segment*2+1 < len(vertices):
return (vertices[segment*2] + vertices[segment*2+1])/2
return self.point_from_proportion(0.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment