Example #1
0
def make_venn3_region_patch(region):
    '''
    Given a venn3 region (as returned from compute_venn3_regions) produces a Patch object,
    depicting the region as a curve.

    >>> centers, radii = solve_venn3_circles((1, 1, 1, 1, 1, 1, 1))
    >>> regions = compute_venn3_regions(centers, radii)
    >>> patches = [make_venn3_region_patch(r) for r in regions]
    '''
    if region is None or len(region[0]) == 0:
        return None
    if region[0] == "CIRCLE":
        return Circle(region[1][0], region[1][1])
    pts, arcs, label_pos = region
    path = [pts[0]]
    for i in range(len(pts)):
        j = (i + 1) % len(pts)
        (center, radius, direction) = arcs[i]
        fromangle = vector_angle_in_degrees(pts[i] - center)
        toangle = vector_angle_in_degrees(pts[j] - center)
        if direction:
            vertices = Path.arc(fromangle, toangle).vertices
        else:
            vertices = Path.arc(toangle, fromangle).vertices
            vertices = vertices[np.arange(len(vertices) - 1, -1, -1)]
        vertices = vertices * radius + center
        path = path + list(vertices[1:])
    codes = [1] + [4] * (len(path) - 1)
    return PathPatch(Path(path, codes))
Example #2
0
def make_venn3_region_patch(region):
    '''
    Given a venn3 region (as returned from compute_venn3_regions) produces a Patch object,
    depicting the region as a curve.

    >>> centers, radii = solve_venn3_circles((1, 1, 1, 1, 1, 1, 1))
    >>> regions = compute_venn3_regions(centers, radii)
    >>> patches = [make_venn3_region_patch(r) for r in regions]
    '''
    if region is None or len(region[0]) == 0:
        return None
    if region[0] == "CIRCLE":
        return Circle(region[1][0], region[1][1])
    pts, arcs, label_pos = region
    path = [pts[0]]
    for i in range(len(pts)):
        j = (i + 1) % len(pts)
        (center, radius, direction) = arcs[i]
        fromangle = vector_angle_in_degrees(pts[i] - center)
        toangle = vector_angle_in_degrees(pts[j] - center)
        if direction:
            vertices = Path.arc(fromangle, toangle).vertices
        else:
            vertices = Path.arc(toangle, fromangle).vertices
            vertices = vertices[np.arange(len(vertices) - 1, -1, -1)]
        vertices = vertices * radius + center
        path = path + list(vertices[1:])
    codes = [1] + [4] * (len(path) - 1)
    return PathPatch(Path(path, codes))
Example #3
0
def curved_arrow_double(theta1, theta2, radius, width_outer, width_inner, origin=(0,0), rel_head_width=1.5,
                        f_abs_head_len=None, r_abs_head_len=None, rel_head_len=0.1, reverse=False):
    """Construct the paths a double-sided reversible curved arrow.

    Returns the paths for both the outer and inner arrows.
    Radius is the distance from the origin to the inside of the outer arrow"""
    if not reverse:
        angle_tip_out = math.radians(theta1)
        angle_tip_in = math.radians(theta2)

        # set the angle swept by the arrowhead
        if f_abs_head_len is None:    # compute arrow head length (angle swept) as a fraction of total length
            f_angle_offset = math.radians((theta2-theta1) * rel_head_len)
        else:
            f_angle_offset = f_abs_head_len
        # set the angle swept by the arrowhead
        if r_abs_head_len is None:  # compute arrow head length (angle swept) as a fraction of total length
            r_angle_offset = math.radians((theta2 - theta1) * rel_head_len)
        else:
            r_angle_offset = r_abs_head_len

        # Define the radii of the inside and outside of the head and tail
        head_out_width = width_outer * (rel_head_width + 1)
        head_in_width = width_inner * (rel_head_width + 1)
        tail_out_radius = radius + width_outer
        tail_in_radius = radius - width_inner

        head_out_in_xy, arrowtip_out_xy, head_out_out_xy = get_isosceles_arrowhead(radius, angle_tip_out,
                                                                  angle_tip_out + f_angle_offset, head_out_width)
        head_in_in_xy, arrowtip_in_xy, head_in_out_xy = get_isosceles_arrowhead(radius, angle_tip_in,
                                                                angle_tip_in - r_angle_offset, head_in_width)

        int_outer, ix_pts_outer = get_intersect_segment_circle(head_out_in_xy, head_out_out_xy, tail_out_radius)
        int_inner, ix_pts_inner = get_intersect_segment_circle(head_in_in_xy, head_in_out_xy, tail_in_radius)
        if int_outer:
            start_outer_arc = math.degrees(cart2pol(*ix_pts_outer[0])[1])
        else:
            start_outer_arc = theta1 + math.degrees(f_angle_offset)
        if int_inner:
            end_inner_arc = math.degrees(cart2pol(*ix_pts_inner[0])[1])
        else:
            end_inner_arc = theta2 - math.degrees(r_angle_offset)

        outer_arc = scale_arc(Path.arc(start_outer_arc, theta2), tail_out_radius)
        middle_arc = scale_arc(path_arc_cw(theta2, theta1), radius)
        inner_arc = scale_arc(Path.arc(theta1, end_inner_arc), tail_in_radius)
        outer_arrowhead = join_points([head_out_out_xy])
        inner_arrowhead = join_points([head_in_in_xy])

        outer_path = shift_path_by_vec(concatenate_paths([outer_arc, middle_arc, outer_arrowhead]), np.array(origin))
        inner_path = shift_path_by_vec(concatenate_paths([middle_arc, inner_arc, inner_arrowhead]), np.array(origin))
        return outer_path, inner_path
    else:
        pass
Example #4
0
def curved_arrow_single(theta1, theta2, radius, width, origin=(0,0), rel_head_width=1.5, rel_head_len=0.1,
                        abs_head_len=None, reverse=False):
    """Construct the path for an irreversible curved arrow"""
    # set the angle swept by the arrowhead
    if abs_head_len is None:  # compute arrow head length (angle swept) as a fraction of total length
        f_angle_offset = math.radians((theta2 - theta1) * rel_head_len)
    else:
        f_angle_offset = abs_head_len

    # Define the radii of the inside and outside of the head and tail
    head_width = width * rel_head_width
    tail_out_radius = radius + width / 2.0
    tail_in_radius = radius - width / 2.0

    if not reverse:
        theta_tip = theta1
        theta_tail = theta2

    else:
        theta_tip = theta2
        theta_tail = theta1
        f_angle_offset = -f_angle_offset

    # head_in_point, arrowhead_point, head_out_point = get_perp_arrowhead(radius, theta_tip, f_angle_offset, width, rel_head_width)
    head_in_point, arrowhead_point, head_out_point = get_isosceles_arrowhead(radius, math.radians(theta_tip),
                                                                             math.radians(theta_tip) + f_angle_offset,
                                                                             head_width)
    int_outer, ix_pts_outer = get_intersect_segment_circle(head_in_point, head_out_point, tail_out_radius)
    int_inner, ix_pts_inner = get_intersect_segment_circle(head_in_point, head_out_point, tail_in_radius)
    if int_outer:
        start = math.degrees(cart2pol(*ix_pts_outer[0])[1])
    else:
        start = theta_tip + math.degrees(f_angle_offset)
        # make head wider if it doesn't intersect both sides of tail
        # return curved_arrow_single(theta1, theta2, radius, width, origin, rel_head_width + 0.1, rel_head_len,
        #                 abs_head_len, reverse)
    if int_inner:
        end = math.degrees(cart2pol(*ix_pts_inner[0])[1])
    else:
        end = theta_tip + math.degrees(f_angle_offset)
        # make head wider if it doesn't intersect both sides of tail
        # return curved_arrow_single(theta1, theta2, radius, width, origin, rel_head_width + 0.1, rel_head_len,
        #                            abs_head_len, reverse)

    if not reverse:
        outer_arc = scale_arc(Path.arc(start, theta_tail), tail_out_radius)
        inner_arc = scale_arc(path_arc_cw(theta_tail, end), tail_in_radius)
    else:
        outer_arc = scale_arc(path_arc_cw(start, theta_tail), tail_out_radius)
        inner_arc = scale_arc(Path.arc(theta_tail, end), tail_in_radius)
    arrowhead = join_points([head_in_point, arrowhead_point, head_out_point])

    return shift_path_by_vec(concatenate_paths([outer_arc, inner_arc, arrowhead]), np.array(origin))
Example #5
0
    def to_mpl_path(self):
        """
        Converts the Region into a matplotlib path
        """
        codes = []
        verts = []
        for seg in self.segments:
            codes.append(MPLPath.MOVETO)
            verts.append((seg["x0"], seg["y0"]))
            if seg["t"] == "P":
                continue
            if seg["t"] == "L":
                codes.append(MPLPath.LINETO)
                verts.append((seg["x1"], seg["y1"]))
            elif seg["t"] == "A":
                x0, y0, x1, y1 = seg["x0"], seg["y0"], seg["x1"], seg["y1"]
                xc, yc = seg["xc"], seg["yc"]
                r = np.sqrt((x0 - xc)**2 + (y0 - yc)**2)
                t0 = np.arctan2((y0 - yc), (x0 - xc))
                t1 = np.arctan2((y1 - yc), (x1 - xc))
                t0 = np.rad2deg(t0)
                t1 = np.rad2deg(t1)
                if t0 < 0:
                    t0 = 360 + t0
                if t1 < 0:
                    t1 = 360 + t1
                if t1 - t0 > 180:
                    t1 -= 360
                if t0 <= t1:
                    arc = MPLPath.arc(t0, t1)
                if t0 > t1:
                    arc = MPLPath.arc(t1, t0)
                arccodes = list(arc.codes[:])
                arcverts = list(arc.vertices[:])
                if t0 > t1:
                    arcverts = arcverts[::-1]
                    arccodes = arccodes[::-1]
                    arccodes[0], arccodes[-1] = arccodes[-1], arccodes[0]
                arcverts = [v * r + np.array([xc, yc]) for v in arcverts]
                codes += arccodes
                verts += arcverts
        if self.fill:
            codes.append(MPLPath.CLOSEPOLY)
            verts.append((0, 0))
            c0, v0 = codes[0], verts[0]
            verts = [
                v for i, v in enumerate(verts) if codes[i] != MPLPath.MOVETO
            ]
            codes = [c for c in codes if c != MPLPath.MOVETO]
            verts.insert(0, v0)
            codes.insert(0, c0)

        return MPLPath(verts, codes)
Example #6
0
def convert_boundery_to_path(points):
    vertices = list()
    codes = list()
    for segment in points:
        ptype, start, end = segment[:3]

        if ptype == "straight":  #ray line intersection with infinite ray from (p_x,p_y) to (inf,p_y) of first point of A
            v, c = next(Path((start, end)).iter_segments())
            vertices.append((v[0], v[1]))
            codes.append(c)
        elif ptype == "arc":  #ray arc intersection with infinite ray from (p_x,p_y) to (p_x+1,p_y) of first point of A
            rel, ccw, arc = segment[3:]
            trans = Affine2D().scale(rel.magnitude).translate(
                arc.center.x, arc.center.y)
            path = Path.arc(
                arc.start_angle, arc.end_angle
            )  # if ccw else Path.arc(arc.end_angle, arc.start_angle,)
            path = path.transformed(trans)

            for v, c in path.iter_segments(curves=False):
                vertices.append((v[0], v[1]))
                codes.append(c)
        else:
            logging.warning(
                "unknown boundary type found (convert_boundery_to_path)")
    codes = [1] + [2] * (len(vertices) - 1)
    ret = Path(vertices, codes=codes, closed=True)  #.cleaned(simplify=True)#
    return ret
Example #7
0
def test_full_arc(offset):
    low = offset
    high = 360 + offset

    path = Path.arc(low, high)
    mins = np.min(path.vertices, axis=0)
    maxs = np.max(path.vertices, axis=0)
    np.testing.assert_allclose(mins, -1)
    assert np.allclose(maxs, 1)
Example #8
0
def test_full_arc(offset):
    low = offset
    high = 360 + offset

    path = Path.arc(low, high)
    mins = np.min(path.vertices, axis=0)
    maxs = np.max(path.vertices, axis=0)
    np.testing.assert_allclose(mins, -1)
    np.testing.assert_allclose(maxs, 1)
Example #9
0
def filled_circular_arc(theta1, theta2, radius, width, origin=(0,0)):
    """Construct the path for a circular arc"""

    # Define the radii of the inside and outside of the arc
    out_radius = radius + width / 2.0
    in_radius = radius - width / 2.0

    outer_arc = scale_arc(Path.arc(theta1, theta2), out_radius)
    inner_arc = scale_arc(path_arc_cw(theta2, theta1), in_radius)

    return shift_path_by_vec(concatenate_paths([outer_arc, inner_arc]), np.array(origin))
Example #10
0
 def _recompute_path(self):
     # Form the outer ring
     arc = Path.arc(theta1=0.0, theta2=360.0)
     # Draw the outer unit circle followed by a reversed and scaled inner circle
     v1 = arc.vertices
     v2 = arc.vertices[::-1] * float(1.0 - self.thick)
     v = np.vstack([v1, v2, v1[0, :], (0, 0)])
     c = np.hstack([arc.codes, arc.codes, Path.MOVETO, Path.CLOSEPOLY])
     c[len(arc.codes)] = Path.MOVETO
     # Final shape acheieved through axis transformation. See _recompute_transform
     self._path = Path(v, c)
Example #11
0
    def bezier_path(self):
        """
        Return ``self`` as a Bezier path.

        This is needed to concatenate arcs, in order to
        create hyperbolic polygons.

        EXAMPLES::

            sage: from sage.plot.arc import Arc
            sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0,
            ....:     'linestyle':'--'}
            sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path()
            Graphics object consisting of 1 graphics primitive

            sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red")
            sage: b = a[0].bezier_path()
            sage: b[0]
            Bezier path from (1.133..., 0.8237...) to (-0.2655..., 0.9911...)
        """
        from sage.plot.bezier_path import BezierPath
        from sage.plot.graphics import Graphics
        from matplotlib.path import Path
        import numpy as np
        ma = self._matplotlib_arc()

        def theta_stretch(theta, scale):
            theta = np.deg2rad(theta)
            x = np.cos(theta)
            y = np.sin(theta)
            return np.rad2deg(np.arctan2(scale * y, x))

        theta1 = theta_stretch(ma.theta1, ma.width / ma.height)
        theta2 = theta_stretch(ma.theta2, ma.width / ma.height)

        pa = ma
        pa._path = Path.arc(theta1, theta2)
        transform = pa.get_transform().get_matrix()
        cA, cC, cE = transform[0]
        cB, cD, cF = transform[1]
        points = []
        for u in pa._path.vertices:
            x, y = list(u)
            points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)]
        cutlist = [points[0:4]]
        N = 4
        while N < len(points):
            cutlist += [points[N:N + 3]]
            N += 3
        g = Graphics()
        opt = self.options()
        opt['fill'] = False
        g.add_primitive(BezierPath(cutlist, opt))
        return g
Example #12
0
    def bezier_path(self):
        """
        Return ``self`` as a Bezier path.

        This is needed to concatenate arcs, in order to
        create hyperbolic polygons.

        EXAMPLES::

            sage: from sage.plot.arc import Arc
            sage: op = {'alpha':1,'thickness':1,'rgbcolor':'blue','zorder':0,
            ....:     'linestyle':'--'}
            sage: Arc(2,3,2.2,2.2,0,2,3,op).bezier_path()
            Graphics object consisting of 1 graphics primitive

            sage: a = arc((0,0),2,1,0,(pi/5,pi/2+pi/12), linestyle="--", color="red")
            sage: b = a[0].bezier_path()
            sage: b[0]
            Bezier path from (1.133..., 0.8237...) to (-0.2655..., 0.9911...)
        """
        from sage.plot.bezier_path import BezierPath
        from sage.plot.graphics import Graphics
        from matplotlib.path import Path
        import numpy as np
        ma = self._matplotlib_arc()
        def theta_stretch(theta, scale):
            theta = np.deg2rad(theta)
            x = np.cos(theta)
            y = np.sin(theta)
            return np.rad2deg(np.arctan2(scale * y, x))
        theta1 = theta_stretch(ma.theta1, ma.width / ma.height)
        theta2 = theta_stretch(ma.theta2, ma.width / ma.height)

        pa = ma
        pa._path = Path.arc(theta1, theta2)
        transform = pa.get_transform().get_matrix()
        cA, cC, cE = transform[0]
        cB, cD, cF = transform[1]
        points = []
        for u in pa._path.vertices:
            x, y = list(u)
            points += [(cA * x + cC * y + cE, cB * x + cD * y + cF)]
        cutlist = [points[0: 4]]
        N = 4
        while N < len(points):
            cutlist += [points[N: N + 3]]
            N += 3
        g = Graphics()
        opt = self.options()
        opt['fill'] = False
        g.add_primitive(BezierPath(cutlist, opt))
        return g
Example #13
0
    def __init__(self,
                 center=(0, 0),
                 r1=0,
                 r2=None,
                 theta1=0,
                 theta2=360,
                 **kwargs):
        """
        Draw a ring centered at *x*, *y* center with inner radius *r1* and
        outer radius *r2* that sweeps *theta1* to *theta2* (in degrees).

        Valid kwargs are:

        %(Patch)s
        """
        patches.Patch.__init__(self, **kwargs)
        self.center = center
        self.r1, self.r2 = r1, r2
        self.theta1, self.theta2 = theta1, theta2

        # Inner and outer rings are connected unless the annulus is complete
        delta = abs(theta2 - theta1)
        if fmod(delta, 360) <= 1e-12 * delta:
            theta1, theta2 = 0, 360
            connector = Path.MOVETO
        else:
            connector = Path.LINETO

        # Form the outer ring
        arc = Path.arc(theta1, theta2)

        if r1 > 0:
            # Partial annulus needs to draw the outter ring
            # followed by a reversed and scaled inner ring
            v1 = arc.vertices
            v2 = arc.vertices[::-1] * float(r1) / r2
            v = numpy.vstack([v1, v2, v1[0, :], (0, 0)])
            c = numpy.hstack([arc.codes, arc.codes, connector, Path.CLOSEPOLY])
            c[len(arc.codes)] = connector
        else:
            # Wedge doesn't need an inner ring
            v = numpy.vstack(
                [arc.vertices, [(0, 0), arc.vertices[0, :], (0, 0)]])
            c = numpy.hstack(
                [arc.codes, [connector, connector, Path.CLOSEPOLY]])

        v *= r2
        v += numpy.array(center)
        self._path = Path(v, c)
        self._patch_transform = transforms.IdentityTransform()
Example #14
0
    def __init__(self,
                 center=(0,0),
                 r1=0,
                 r2=None,
                 theta1=0,
                 theta2=360,
                 **kwargs
                 ):
        """
        Draw a ring centered at *x*, *y* center with inner radius *r1* and
        outer radius *r2* that sweeps *theta1* to *theta2* (in degrees).

        Valid kwargs are:

        %(Patch)s
        """
        patches.Patch.__init__(self, **kwargs)
        self.center = center
        self.r1, self.r2 = r1,r2
        self.theta1, self.theta2 = theta1,theta2

        # Inner and outer rings are connected unless the annulus is complete
        delta=abs(theta2-theta1)
        if fmod(delta,360)<=1e-12*delta:
            theta1,theta2 = 0,360
            connector = Path.MOVETO
        else:
            connector = Path.LINETO

        # Form the outer ring
        arc = Path.arc(theta1,theta2)

        if r1 > 0:
            # Partial annulus needs to draw the outter ring
            # followed by a reversed and scaled inner ring
            v1 = arc.vertices
            v2 = arc.vertices[::-1]*float(r1)/r2
            v = numpy.vstack([v1,v2,v1[0,:],(0,0)])
            c = numpy.hstack([arc.codes,arc.codes,connector,Path.CLOSEPOLY])
            c[len(arc.codes)]=connector
        else:
            # Wedge doesn't need an inner ring
            v = numpy.vstack([arc.vertices,[(0,0),arc.vertices[0,:],(0,0)]])
            c = numpy.hstack([arc.codes,[connector,connector,Path.CLOSEPOLY]])

        v *= r2
        v += numpy.array(center)
        self._path = Path(v,c)
        self._patch_transform = transforms.IdentityTransform()
Example #15
0
def path_arc_cw(theta1, theta2):
    """used if theta1 >= theta2"""
    # construct the normal arc ccw
    arc1 = Path.arc(theta2, theta1)

    # flip the vertices and control points
    verts = list(arc1.vertices[0::2])
    verts.reverse()
    controls = list(arc1.vertices[1::2])
    controls.reverse()
    new_verts = []
    for i in range(len(controls)):
        new_verts.append(verts[i])
        new_verts.append(controls[i])
    new_verts.append(verts[-1])
    return Path(np.array(new_verts), arc1.codes)
Example #16
0
 def _recompute_path(self):
     # Form the outer ring
     arc = Path.arc(theta1=0.0, theta2=360.0)
     print(f'{arc=}')
     # Draw the outer unit circle followed by a reversed and scaled inner circle
     v1 = arc.vertices
     v2 = np.zeros_like(v1)
     v2[:, 0] = v1[::-1, 0] * float(1.0 - self.thick[0])
     v2[:, 1] = v1[::-1, 1] * float(1.0 - self.thick[1])
     print(f'{v1=} {v2=}')
     v = np.vstack([v1, v2, v1[0, :], (0, 0)])
     print(f'{v=}')
     c = np.hstack([arc.codes, arc.codes, Path.MOVETO, Path.CLOSEPOLY])
     print(f'{c=}')
     c[len(arc.codes)] = Path.MOVETO
     # Final shape acheieved through axis transformation. See _recompute_transform
     self._path = Path(v, c)
Example #17
0
    def draw(self, renderer):
        """
        Ellipses are normally drawn using an approximation that uses
        eight cubic bezier splines.  The error of this approximation
        is 1.89818e-6, according to this unverified source:

          Lancaster, Don.  Approximating a Circle or an Ellipse Using
          Four Bezier Cubic Splines.

          http://www.tinaja.com/glib/ellipse4.pdf

        There is a use case where very large ellipses must be drawn
        with very high accuracy, and it is too expensive to render the
        entire ellipse with enough segments (either splines or line
        segments).  Therefore, in the case where either radius of the
        ellipse is large enough that the error of the spline
        approximation will be visible (greater than one pixel offset
        from the ideal), a different technique is used.

        In that case, only the visible parts of the ellipse are drawn,
        with each visible arc using a fixed number of spline segments
        (8).  The algorithm proceeds as follows:

          1. The points where the ellipse intersects the axes bounding
          box are located.  (This is done be performing an inverse
          transformation on the axes bbox such that it is relative to
          the unit circle -- this makes the intersection calculation
          much easier than doing rotated ellipse intersection
          directly).

          This uses the "line intersecting a circle" algorithm from:

            Vince, John.  Geometry for Computer Graphics: Formulae,
            Examples & Proofs.  London: Springer-Verlag, 2005.

          2. The angles of each of the intersection points are
          calculated.

          3. Proceeding counterclockwise starting in the positive
          x-direction, each of the visible arc-segments between the
          pairs of vertices are drawn using the bezier arc
          approximation technique implemented in Path.arc().
        """
        if not hasattr(self, 'axes'):
            raise RuntimeError('Arcs can only be used in Axes instances')

        self._recompute_transform()

        # Get the width and height in pixels
        width = self.convert_xunits(self.width)
        height = self.convert_yunits(self.height)
        width, height = self.get_transform().transform_point(
            (width, height))
        inv_error = (1.0 / 1.89818e-6) * 0.5

        if width < inv_error and height < inv_error:
            self._path = Path.arc(self.theta1, self.theta2)
            return Patch.draw(self, renderer)

        def iter_circle_intersect_on_line(x0, y0, x1, y1):
            dx = x1 - x0
            dy = y1 - y0
            dr2 = dx*dx + dy*dy
            D = x0*y1 - x1*y0
            D2 = D*D
            discrim = dr2 - D2

            # Single (tangential) intersection
            if discrim == 0.0:
                x = (D*dy) / dr2
                y = (-D*dx) / dr2
                yield x, y
            elif discrim > 0.0:
                # The definition of "sign" here is different from
                # npy.sign: we never want to get 0.0
                if dy < 0.0:
                    sign_dy = -1.0
                else:
                    sign_dy = 1.0
                sqrt_discrim = npy.sqrt(discrim)
                for sign in (1., -1.):
                    x = (D*dy + sign * sign_dy * dx * sqrt_discrim) / dr2
                    y = (-D*dx + sign * npy.abs(dy) * sqrt_discrim) / dr2
                    yield x, y

        def iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
            epsilon = 1e-9
            if x1 < x0:
                x0e, x1e = x1, x0
            else:
                x0e, x1e = x0, x1
            if y1 < y0:
                y0e, y1e = y1, y0
            else:
                y0e, y1e = y0, y1
            x0e -= epsilon
            y0e -= epsilon
            x1e += epsilon
            y1e += epsilon
            for x, y in iter_circle_intersect_on_line(x0, y0, x1, y1):
                if x >= x0e and x <= x1e and y >= y0e and y <= y1e:
                    yield x, y

        # Transforms the axes box_path so that it is relative to the unit
        # circle in the same way that it is relative to the desired
        # ellipse.
        box_path = Path.unit_rectangle()
        box_path_transform = transforms.BboxTransformTo(self.axes.bbox) + \
            self.get_transform().inverted()
        box_path = box_path.transformed(box_path_transform)

        PI = npy.pi
        TWOPI = PI * 2.0
        RAD2DEG = 180.0 / PI
        DEG2RAD = PI / 180.0
        theta1 = self.theta1
        theta2 = self.theta2
        thetas = {}
        # For each of the point pairs, there is a line segment
        for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
            x0, y0 = p0
            x1, y1 = p1
            for x, y in iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
                theta = npy.arccos(x)
                if y < 0:
                    theta = TWOPI - theta
                # Convert radians to angles
                theta *= RAD2DEG
                if theta > theta1 and theta < theta2:
                    thetas[theta] = None

        thetas = thetas.keys()
        thetas.sort()
        thetas.append(theta2)

        last_theta = theta1
        theta1_rad = theta1 * DEG2RAD
        inside = box_path.contains_point((npy.cos(theta1_rad), npy.sin(theta1_rad)))
        for theta in thetas:
            if inside:
                self._path = Path.arc(last_theta, theta, 8)
                Patch.draw(self, renderer)
                inside = False
            else:
                inside = True
            last_theta = theta
Example #18
0
    def _render_patches(self,
                        axes,
                        aa_pixel_size=0,
                        rotation=(1, 0, 0, 0),
                        ambient_light=0,
                        directional_light=(-.1, -.25, -1),
                        **kwargs):
        rotation = np.asarray(rotation)

        start_points, end_points, widths, colors = mesh.unfoldProperties(
            [self.start_points, self.end_points, self.widths, self.colors])

        # rotate into scene orientation
        start_points = math.quatrot(rotation[np.newaxis], start_points)
        end_points = math.quatrot(rotation[np.newaxis], end_points)
        midpoints = 0.5 * (start_points + end_points)
        zs = midpoints[:, 2]

        # calculate the vector perpendicular to each line segment
        deltas = end_points[:, :2] - start_points[:, :2]
        perps = np.array([-deltas[:, 1], deltas[:, 0]]).T
        perps /= np.linalg.norm(perps, axis=-1, keepdims=True)
        perps *= 0.5 * widths
        perps[np.any(np.logical_not(np.isfinite(perps)), axis=-1)] = (1, 0)

        # angle of the vector perpendicular to each line segment
        angles = np.arctan2(perps[:, 1], perps[:, 0]) + np.pi
        angles[np.logical_not(np.isfinite(angles))] = 0
        angles_degrees = angles * 180 / np.pi

        # construct rectangles with offset vertices
        rectangles = [
            start_points[:, :2] - perps, end_points[:, :2] - perps,
            end_points[:, :2] + perps, start_points[:, :2] + perps
        ]

        if aa_pixel_size:
            for elt in rectangles:
                elt -= midpoints[:, :2]
                elt += np.sign(elt) * aa_pixel_size
                elt += midpoints[:, :2]

        patches = []
        for (z, angle, width, start, end, a, b, c,
             d) in zip(zs, angles_degrees, widths[:, 0], start_points[:, :2],
                       end_points[:, :2], *rectangles):
            arc = Path.arc(angle, angle + 180)
            commands = [Path.MOVETO, Path.LINETO]
            vertices = [a, b]

            for (pos, cmd) in arc.iter_segments():
                cmd = cmd if cmd != Path.MOVETO else Path.LINETO
                if cmd == Path.STOP:
                    continue
                pos = pos.reshape((-1, 2)) * width * 0.5
                commands.extend(pos.shape[0] * [cmd])
                vertices.extend(end[np.newaxis] + pos)

            commands.append(Path.LINETO)
            vertices.append(c)

            commands.append(Path.LINETO)
            vertices.append(d)
            for (pos, cmd) in arc.iter_segments():
                cmd = cmd if cmd != Path.MOVETO else Path.LINETO
                if cmd == Path.STOP:
                    continue
                pos = pos.reshape((-1, 2)) * width * 0.5
                commands.extend(pos.shape[0] * [cmd])
                vertices.extend(start[np.newaxis] - pos)

            commands.append(Path.CLOSEPOLY)
            vertices.append(a)

            path = Path(vertices, commands)
            patches.append(PathPatch(path, zorder=z))

        return [(patches, colors)]
Example #19
0
fig, ax = plt.subplots(subplot_kw={'aspect': 'equal'})

e3 = EllipticalShell(center=(0, 0),
                     width=1,
                     height=1,
                     thick=(0.5, 0.0),
                     angle=0)
ax.add_patch(e3)
e3.set_facecolor((1, 0, 0))

e1 = Ellipse(xy=(0, 0), width=0.1, height=1, angle=0)
ax.add_patch(e1)
e1.set_facecolor((0, 0, 1))

e2 = Ellipse(xy=(0, 0), width=0.1, height=1, angle=90)
ax.add_patch(e2)
e2.set_facecolor((0, 1, 0))

ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)

arc = Path.arc(theta1=0.0, theta2=360.0)
v1 = arc.vertices
v2 = arc.vertices[::-1] * float(
    1.0 - 0.5)  # self.thick is fractional thickness

ax.scatter(v1[:, 0], v1[:, 1])
ax.scatter(v2[:, 0], v2[:, 1])

plt.show()
Example #20
0
def _parse_path(pathdef, current_pos):
    # In the SVG specs, initial movetos are absolute, even if
    # specified as 'm'. This is the default behavior here as well.
    # But if you pass in a current_pos variable, the initial moveto
    # will be relative to that current_pos. This is useful.
    elements = list(_tokenize_path(pathdef))
    # Reverse for easy use of .pop()
    elements.reverse()

    start_pos = None
    command = None

    while elements:

        # 1. Determine the current command

        if elements[-1] in COMMANDS:
            # New command.
            last_command = command  # Used by S and T
            command = elements.pop()
            absolute = command in UPPERCASE
            command = command.upper()
        else:
            # Implicit command.
            # If this element starts with numbers, it is an implicit command
            # and we don't change the command. Check that it's allowed:
            if command is None:
                raise ValueError(
                    "Unallowed implicit command in {}, position {}".format(
                    pathdef, len(pathdef.split()) - len(elements)))
            last_command = command  # Used by S and T


        # 2. Parse the current command

        # MOVETO
        if command == 'M':
            pos = _next_pos(elements)
            if absolute:
                current_pos = pos
            else:
                current_pos += pos

            # when M is called, reset start_pos
            # This behavior of Z is defined in svg spec:
            # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
            start_pos = current_pos

            yield COMMAND_CODES['M'], [(current_pos.real, current_pos.imag)]

            # Implicit moveto commands are treated as lineto commands.
            # So we set command to lineto here, in case there are
            # further implicit commands after this moveto.
            command = 'L'

        # CLOSEPATH
        elif command == 'Z':
            # path closure
            if current_pos != start_pos:
                verts = [(start_pos.real, start_pos.imag)]
                yield COMMAND_CODES['L'], verts

            # mpl.Path: a point is required but ignored
            verts = [(start_pos.real, start_pos.imag)]
            yield COMMAND_CODES['Z'], verts

            current_pos = start_pos
            start_pos = None
            command = None  # You can't have implicit commands after closing.

        # LINETO
        elif command == 'L':
            pos = _next_pos(elements)
            if not absolute:
                pos += current_pos
            verts = [(pos.real, pos.imag)]
            yield COMMAND_CODES['L'], verts
            current_pos = pos

        # HORIZONTAL_PATHTO
        elif command == 'H':
            x = elements.pop()
            pos = float(x) + current_pos.imag * 1j
            if not absolute:
                pos += current_pos.real
            verts = [(pos.real, pos.imag)]
            yield COMMAND_CODES['H'], verts
            current_pos = pos

        # VERTICAL_PATHTO
        elif command == 'V':
            y = elements.pop()
            pos = current_pos.real + float(y) * 1j
            if not absolute:
                pos += current_pos.imag * 1j
            verts = [(pos.real, pos.imag)]
            yield COMMAND_CODES['V'], verts
            current_pos = pos

        # CUBIC_BEZIER
        elif command == 'C':
            control1 = _next_pos(elements)
            control2 = _next_pos(elements)
            end = _next_pos(elements)
            if not absolute:
                control1 += current_pos
                control2 += current_pos
                end += current_pos
            verts = [
                 (control1.real, control1.imag),
                 (control2.real, control2.imag),
                 (end.real, end.imag)
            ]
            yield COMMAND_CODES['C'], verts
            current_pos = end

        # SMOOTH_CUBIC_BEZIER
        elif command == 'S':
            # Smooth curve. First control point is the "reflection" of
            # the second control point in the previous path.
            if last_command not in 'CS':
                # If there is no previous command or if the previous command
                # was not an C, c, S or s, assume the first control point is
                # coincident with the current point.
                control1 = current_pos
            else:
                # The first control point is assumed to be the reflection of
                # the second control point on the previous command relative
                # to the current point.
                last_control = control2
                control1 = current_pos + current_pos - last_control
            control2 = _next_pos(elements)
            end = _next_pos(elements)
            if not absolute:
                control2 += current_pos
                end += current_pos
            verts = [
                (control1.real, control1.imag),
                (control2.real, control2.imag),
                (end.real, end.imag)
            ]
            yield COMMAND_CODES['S'], verts
            current_pos = end

        # QUADRATIC_BEZIER
        elif command == 'Q':
            control = _next_pos(elements)
            end = _next_pos(elements)
            if not absolute:
                control += current_pos
                end += current_pos
            verts = [
                (control.real, control.imag),
                (end.real, end.imag)
            ]
            yield COMMAND_CODES['Q'], verts
            current_pos = end

        # SMOOTH_QUADRATIC_BEZIER
        elif command == 'T':
            # Smooth curve. Control point is the "reflection" of
            # the second control point in the previous path.
            if last_command not in 'QT':
                # If there is no previous command or if the previous command
                # was not an Q, q, T or t, assume the first control point is
                # coincident with the current point.
                control = current_pos
            else:
                # The control point is assumed to be the reflection of
                # the control point on the previous command relative
                # to the current point.
                last_control = control
                control = current_pos + current_pos - last_control
            end = _next_pos(elements)
            if not absolute:
                end += current_pos
            verts = [
                (control.real, control.imag),
                (end.real, end.imag)
            ]
            yield COMMAND_CODES['T'], verts
            current_pos = end

        # ELLIPTICAL_ARC
        elif command == 'A':
            radius = _next_pos(elements)
            rotation = float(elements.pop())
            large = float(elements.pop())
            sweep = float(elements.pop())
            end = _next_pos(elements)
            if not absolute:
                end += current_pos

            center, theta1, theta2 = endpoint_to_center(
                current_pos,
                radius,
                rotation,
                large,
                sweep,
                end
            )

            # Create an arc on the unit circle
            if theta2 > theta1:
                arc = Path.arc(theta1=theta1, theta2=theta2)
            else:
                arc = Path.arc(theta1=theta2, theta2=theta1)

            # Transform it into an elliptical arc:
            # * scale the minor and major axes
            # * translate it to the center
            # * rotate the x-axis of the ellipse from the x-axis of the current
            #   coordinate system
            trans = (
                transforms.Affine2D()
                    .scale(radius.real, radius.imag)
                    .translate(center.real, center.imag)
                    .rotate_deg_around(center.real, center.imag, rotation)
            )
            arc = trans.transform_path(arc)

            verts = np.array(arc.vertices)
            codes = np.array(arc.codes)
            if sweep:
                # mysterious hack needed to render properly when sweeping the
                # arc angle in the "positive" angular direction
                yield codes[1:], verts[1:, :]
            else:
                yield codes, verts

            current_pos = end
def plot_working_area(l_1,
                      l_2,
                      theta_1_min,
                      theta_1_max,
                      theta_2_min,
                      theta_2_max,
                      plot=True):
    ################################################################
    ################  Transform angles to radians   ################
    ################################################################
    theta_1_min = math.radians(theta_1_min)
    theta_1_max = math.radians(theta_1_max)
    theta_2_min = math.radians(theta_2_min)
    theta_2_max = math.radians(theta_2_max)
    ################################################################
    ################     Calculate Working Area     ################
    ################################################################
    wa = l_1 * l_2 * (math.cos(theta_2_min) -
                      math.cos(theta_2_max)) * (theta_1_max - theta_1_min)
    ################################################################
    ################           Setup Graph          ################
    ################################################################
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.spines['left'].set_position('center')
    ax.spines['bottom'].set_position('center')
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')
    ax.autoscale(True, 'both')
    ################################################################
    ################        Common Variables        ################
    ################################################################
    o = [0, 0]
    ################################################################
    ################        Link 2 at minimum       ################
    ################################################################
    start_point = forward_kinematics(l_1, l_2, theta_1_min, theta_2_min, True)
    end_point = forward_kinematics(l_1, l_2, theta_1_max, theta_2_min, True)
    radius = math.dist(end_point, o)
    start_angle = angle(start_point, [1, 0])
    end_angle = angle(end_point, [1, 0])
    ################################################################
    current_path = Path.arc(start_angle, end_angle)
    new_verts = current_path.deepcopy().vertices
    for new_vert in new_verts:
        new_vert[0] = new_vert[0] * radius
        new_vert[1] = new_vert[1] * radius
    new_verts = new_verts[:]
    current_path = Path(new_verts, current_path.deepcopy().codes[:])
    paths.append(current_path)
    patches.append(PathPatch(current_path))
    ax.add_patch(patches[0])
    ################################################################
    ################        Link 2 at maximum       ################
    ################################################################
    end_point = forward_kinematics(l_1, l_2, theta_1_min, theta_2_max, True)
    start_point = forward_kinematics(l_1, l_2, theta_1_max, theta_2_max, True)
    radius = math.dist(end_point, o)
    start_angle = angle_clockwise(start_point, [1, 0])
    end_angle = angle_clockwise(end_point, [1, 0])
    ################################################################
    current_path = Path.arc(start_angle, end_angle)
    new_verts = current_path.deepcopy().vertices
    for new_vert in new_verts:
        new_vert[0] = new_vert[0] * radius
        new_vert[1] = new_vert[1] * radius * -1
    new_verts = new_verts[:]
    current_path = Path(new_verts, current_path.deepcopy().codes[:])
    paths.append(current_path)
    patches.append(PathPatch(current_path))
    ax.add_patch(patches[1])
    ################################################################
    ################        Link 1 at minimum       ################
    ################################################################
    center = (l_1 * math.cos(theta_1_min), l_1 * math.sin(theta_1_min))
    end_point = forward_kinematics(l_1, l_2, theta_1_min, theta_2_min, True)
    end_point = [end_point[0] - center[0], end_point[1] - center[1]]
    start_point = forward_kinematics(l_1, l_2, theta_1_min, theta_2_max, True)
    start_point = [start_point[0] - center[0], start_point[1] - center[1]]
    radius = l_2
    start_angle = angle_clockwise(start_point, [1, 0])
    end_angle = angle_clockwise(end_point, [1, 0])
    ################################################################
    current_path = Path.arc(start_angle, end_angle)
    new_verts = current_path.deepcopy().vertices
    for new_vert in new_verts:
        new_vert[0] = (new_vert[0] * radius + center[0])
        new_vert[1] = (new_vert[1] * radius + center[1]) * -1
    new_verts[np.shape(new_verts)[0] - 1] = paths[0].vertices[0]
    new_verts = new_verts[:]

    current_path = Path(new_verts, current_path.deepcopy().codes[:])
    paths.append(current_path)
    patches.append(PathPatch(current_path))
    ax.add_patch(patches[2])
    ################################################################
    ################        Link 1 at maximum       ################
    ################################################################
    center = (l_1 * math.cos(theta_1_max), l_1 * math.sin(theta_1_max))
    start_point = forward_kinematics(l_1, l_2, theta_1_max, theta_2_min, True)
    start_point = [start_point[0] - center[0], start_point[1] - center[1]]
    end_point = forward_kinematics(l_1, l_2, theta_1_max, theta_2_max, True)
    end_point = [end_point[0] - center[0], end_point[1] - center[1]]
    radius = l_2
    start_angle = angle(start_point, [1, 0])
    end_angle = angle(end_point, [1, 0])
    ################################################################
    current_path = Path.arc(start_angle, end_angle)
    new_verts = current_path.deepcopy().vertices
    for new_vert in new_verts:
        new_vert[0] = new_vert[0] * radius + center[0]
        new_vert[1] = new_vert[1] * radius + center[1]
    new_verts = new_verts[:]
    current_path = Path(new_verts, current_path.deepcopy().codes[:])
    paths.append(current_path)
    patches.append(PathPatch(current_path))
    ax.add_patch(patches[3])
    ################################################################
    ################           Plot curves          ################
    ################################################################
    ax.set_title("Working Area = {wa:.3f}".format(wa=wa))

    new_path = Path.make_compound_path(
        paths[0],
        paths[3],
        paths[1],
        paths[2],
    )

    old_vertices = new_path.deepcopy().vertices
    old_codes = new_path.deepcopy().codes
    new_path_vertices = []
    new_path_codes = []

    for i, old_vertex in enumerate(old_vertices, start=0):
        if (i == 0):
            new_path_vertices.append(old_vertex)
            new_path_codes.append(old_codes[i])
        else:
            if (old_codes[i] != 1):
                new_path_vertices.append(old_vertex)
                new_path_codes.append(old_codes[i])

    new_path = Path(new_path_vertices, new_path_codes)

    patches[0].remove()
    patches[1].remove()
    patches[2].remove()
    patches[3].remove()

    path_patch = PathPatch(new_path, fill=False, hatch='/', clip_on=True)
    patches.append(path_patch)
    ax.add_patch(path_patch)
    if (plot == True):
        plt.show()
    return [ax, plt, fig]
Example #22
0
 def set_theta2(self, angle):
     self.theta2 = angle
     self._path = Path.arc(self.theta1, self.theta2)
Example #23
0
 def path(self):
     path = Path.arc(self.start_angle, self.end_angle)
     transform = Affine2D().scale(self.radius).translate(*self.center.xy())
     return path.transformed(transform)
Example #24
0
    def draw(self, renderer):
        """
        Ellipses are normally drawn using an approximation that uses
        eight cubic bezier splines.  The error of this approximation
        is 1.89818e-6, according to this unverified source:

          Lancaster, Don.  Approximating a Circle or an Ellipse Using
          Four Bezier Cubic Splines.

          http://www.tinaja.com/glib/ellipse4.pdf

        There is a use case where very large ellipses must be drawn
        with very high accuracy, and it is too expensive to render the
        entire ellipse with enough segments (either splines or line
        segments).  Therefore, in the case where either radius of the
        ellipse is large enough that the error of the spline
        approximation will be visible (greater than one pixel offset
        from the ideal), a different technique is used.

        In that case, only the visible parts of the ellipse are drawn,
        with each visible arc using a fixed number of spline segments
        (8).  The algorithm proceeds as follows:

          1. The points where the ellipse intersects the axes bounding
          box are located.  (This is done be performing an inverse
          transformation on the axes bbox such that it is relative to
          the unit circle -- this makes the intersection calculation
          much easier than doing rotated ellipse intersection
          directly).

          This uses the "line intersecting a circle" algorithm from:

            Vince, John.  Geometry for Computer Graphics: Formulae,
            Examples & Proofs.  London: Springer-Verlag, 2005.

          2. The angles of each of the intersection points are
          calculated.

          3. Proceeding counterclockwise starting in the positive
          x-direction, each of the visible arc-segments between the
          pairs of vertices are drawn using the bezier arc
          approximation technique implemented in Path.arc().
        """
        if not hasattr(self, 'axes'):
            raise RuntimeError('Arcs can only be used in Axes instances')

        self._recompute_transform()

        # Get the width and height in pixels
        width = self.convert_xunits(self.width)
        height = self.convert_yunits(self.height)
        width, height = self.get_transform().transform_point(
            (width, height))
        inv_error = (1.0 / 1.89818e-6) * 0.5

        if width < inv_error and height < inv_error:
            self._path = Path.arc(self.theta1, self.theta2)
            return Patch.draw(self, renderer)

        def iter_circle_intersect_on_line(x0, y0, x1, y1):
            dx = x1 - x0
            dy = y1 - y0
            dr2 = dx*dx + dy*dy
            D = x0*y1 - x1*y0
            D2 = D*D
            discrim = dr2 - D2

            # Single (tangential) intersection
            if discrim == 0.0:
                x = (D*dy) / dr2
                y = (-D*dx) / dr2
                yield x, y
            elif discrim > 0.0:
                # The definition of "sign" here is different from
                # np.sign: we never want to get 0.0
                if dy < 0.0:
                    sign_dy = -1.0
                else:
                    sign_dy = 1.0
                sqrt_discrim = np.sqrt(discrim)
                for sign in (1., -1.):
                    x = (D*dy + sign * sign_dy * dx * sqrt_discrim) / dr2
                    y = (-D*dx + sign * np.abs(dy) * sqrt_discrim) / dr2
                    yield x, y

        def iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
            epsilon = 1e-9
            if x1 < x0:
                x0e, x1e = x1, x0
            else:
                x0e, x1e = x0, x1
            if y1 < y0:
                y0e, y1e = y1, y0
            else:
                y0e, y1e = y0, y1
            x0e -= epsilon
            y0e -= epsilon
            x1e += epsilon
            y1e += epsilon
            for x, y in iter_circle_intersect_on_line(x0, y0, x1, y1):
                if x >= x0e and x <= x1e and y >= y0e and y <= y1e:
                    yield x, y

        # Transforms the axes box_path so that it is relative to the unit
        # circle in the same way that it is relative to the desired
        # ellipse.
        box_path = Path.unit_rectangle()
        box_path_transform = transforms.BboxTransformTo(self.axes.bbox) + \
            self.get_transform().inverted()
        box_path = box_path.transformed(box_path_transform)

        PI = np.pi
        TWOPI = PI * 2.0
        RAD2DEG = 180.0 / PI
        DEG2RAD = PI / 180.0
        theta1 = self.theta1
        theta2 = self.theta2
        thetas = {}
        # For each of the point pairs, there is a line segment
        for p0, p1 in zip(box_path.vertices[:-1], box_path.vertices[1:]):
            x0, y0 = p0
            x1, y1 = p1
            for x, y in iter_circle_intersect_on_line_seg(x0, y0, x1, y1):
                theta = np.arccos(x)
                if y < 0:
                    theta = TWOPI - theta
                # Convert radians to angles
                theta *= RAD2DEG
                if theta > theta1 and theta < theta2:
                    thetas[theta] = None

        thetas = thetas.keys()
        thetas.sort()
        thetas.append(theta2)

        last_theta = theta1
        theta1_rad = theta1 * DEG2RAD
        inside = box_path.contains_point((np.cos(theta1_rad), np.sin(theta1_rad)))
        for theta in thetas:
            if inside:
                self._path = Path.arc(last_theta, theta, 8)
                Patch.draw(self, renderer)
                inside = False
            else:
                inside = True
            last_theta = theta
Example #25
0
def compress_points(points):
    bi, bj, bx, by = get_boundary_intersections(points)

    f = bi == bj
    alone_points = points[bi[f]]
    alone_paths = [Path.circle(xy, 0.5) for xy in alone_points]

    edge_lists = [[] for i in range(len(points))]
    n = 0
    for i, j, x, y in zip(bi, bj, bx, by):
        if i != j:
            edge_lists[j].append((i, x, y))
            n += 1

    print("%s points in total: %s edges, %s alone points" %
          (len(points), n, len(alone_points)))

    def patan2(dy, dx):
        """
        Return pseudo-arctangent of dy/dx such that
            patan2(y1, x1) < patan2(y2, x2)
            if and only if
            atan2(y1, x1) < atan2(y2, x2)
        """
        if dy > 0 and dx > 0:
            return (0, dy - dx)
        elif dy > 0 and dx <= 0:
            return (1, -dy - dx)
        elif dy <= 0 and dx > 0:
            return (2, dx - dy)
        else:
            return (3, dx + dy)

    def shift(u, v):
        if v < u:
            return (v[0] + 4, v[1])
        else:
            return v

    def pop_next(i, ox, oy):
        def local_patan2(y, x):
            return patan2(y - points[i, 1], x - points[i, 0])
        u = local_patan2(oy, ox)
        j = min(range(len(edge_lists[i])),
                key=lambda j: shift(u, local_patan2(edge_lists[i][j][2],
                                                    edge_lists[i][j][1])))
        return edge_lists[i].pop(j)

    paths = []
    # print("<path fill=\"black\" fillrule=\"wind\">")
    while n > 0:
        assert sum(len(e) for e in edge_lists) == n
        i = 0
        while not edge_lists[i]:
            i += 1
        start = i
        j, ox, oy = edge_lists[i].pop(0)
        startx, starty = ox, oy
        ux, uy = ox, oy
        # path = ['%s %s m' % (startx, starty)]
        path_vert_lists = [[[startx, starty]]]
        path_code_lists = [[Path.MOVETO]]

        n -= 1
        while j != start:
            i = j
            j, vx, vy = pop_next(i, ux, uy)
            n -= 1
            # path.append(
            #     '%s 0 0 %s %s %s %s %s a' %
            #     (R, R, points[i, 0], points[i, 1], ox, oy))
            ox, oy = points[i]
            theta1 = np.arctan2(uy - oy, ux - ox)
            theta2 = np.arctan2(vy - oy, vx - ox)
            a = Path.arc(theta1 * 180 / np.pi, theta2 * 180 / np.pi)
            a = a.transformed(Affine2D().scale(0.5).translate(ox, oy))
            path_vert_lists.append(a._vertices[1:])
            path_code_lists.append(a._codes[1:])
            ux, uy = vx, vy
        # path.append(
        #     '%s 0 0 %s %s %s %s %s a' %
        #     (R, R, points[j, 0], points[j, 1], startx, starty))
        ox, oy = points[j]
        theta1 = np.arctan2(uy - oy, ux - ox)
        theta2 = np.arctan2(starty - oy, startx - ox)
        a = Path.arc(theta1 * 180 / np.pi, theta2 * 180 / np.pi)
        a = a.transformed(Affine2D().scale(0.5).translate(ox, oy))
        path_vert_lists.append(a._vertices[1:])
        path_code_lists.append(a._codes[1:])
        # print('\n'.join(path))
        paths.append(
            Path(np.concatenate(path_vert_lists),
                 np.concatenate(path_code_lists).astype(Path.code_type)))
    # print("</path>")
    return Path.make_compound_path(*(alone_paths + paths))
Example #26
0
    def _render_patches(self, axes, aa_pixel_size=0, **kwargs):
        result = []

        vertices = self.vertices
        # distance vector from each vertex to the next vertex in a given shape
        delta_verts = np.roll(vertices, -1, axis=0) - vertices
        delta_verts /= np.linalg.norm(delta_verts, axis=-1, keepdims=True)
        # the normal vector of the edge originating at each vertex
        vert_normals = np.transpose([delta_verts[:, 1], -delta_verts[:, 0]])

        # start and end angles for the arc around each vertex, in degrees
        degree_ends = 180/np.pi*np.arctan2(vert_normals[..., 1], vert_normals[..., 0])
        degree_starts = np.roll(degree_ends, 1, axis=0)

        radius = self.radius
        outline = self.outline
        delta_outline = radius - outline

        if outline > 0:
            # sketch out the outline, using the full radius
            commands = [Path.MOVETO]
            positions = [vertices[-1] + vert_normals[-1]*radius]
            for (vert, norm, degrees_start, degrees_end) in zip(
                    vertices, np.roll(vert_normals, 1, axis=0),
                    degree_starts, degree_ends):
                commands.append(Path.LINETO)
                positions.append(vert + norm*radius)
                arc = Path.arc(degrees_start, degrees_end)
                for (pos, cmd) in arc.iter_segments():
                    if cmd in {Path.STOP, Path.MOVETO}:
                        continue
                    pos = pos.reshape((-1, 2))
                    commands.extend(pos.shape[0]*[cmd])
                    positions.extend(vert[np.newaxis] + pos*radius)

            # repeat the outline path creation but in reverse to make
            # the hole for the colored portion of the shape
            commands.append(Path.MOVETO)
            positions.append(vertices[0] + vert_normals[-1]*delta_outline)
            for (vert, norm, degrees_end, degrees_start) in zip(
                    vertices[::-1], vert_normals[::-1],
                    degree_ends[::-1], degree_starts[::-1]):
                commands.append(Path.LINETO)
                positions.append(vert + norm*delta_outline)
                arc = Path.arc(degrees_start, degrees_end)
                for (pos, cmd) in reversed(list(arc.iter_segments())):
                    if cmd in {Path.STOP, Path.MOVETO}:
                        continue
                    pos = pos.reshape((-1, 2))[::-1]
                    commands.extend(pos.shape[0]*[cmd])
                    positions.extend(vert[np.newaxis] + pos*delta_outline)
            path = Path(positions, commands)

            patches = []
            for (position, angle) in zip(self.positions, self.angles):
                tf = Affine2D().rotate(angle).translate(*position)
                patches.append(PathPatch(path.transformed(tf)))
            outline_colors = np.zeros_like(self.colors)
            outline_colors[:, 3] = self.colors[:, 3]

            result.append((patches, outline_colors))

            vertices += np.sign(vertices)*aa_pixel_size

        # create the path for the filled/colored portion of the shape
        commands = [Path.MOVETO]
        positions = [vertices[-1] + vert_normals[-1]*delta_outline]
        for (vert, norm, degrees_start, degrees_end) in zip(
                vertices, np.roll(vert_normals, 1, axis=0), degree_starts, degree_ends):
            commands.append(Path.LINETO)
            positions.append(vert + norm*delta_outline)
            arc = Path.arc(degrees_start, degrees_end)
            for (pos, cmd) in arc.iter_segments():
                if cmd in {Path.STOP, Path.MOVETO}:
                    continue
                pos = pos.reshape((-1, 2))*delta_outline
                commands.extend(pos.shape[0]*[cmd])
                positions.extend(vert[np.newaxis] + pos)
        path = Path(positions, commands)

        patches = []
        for (position, angle) in zip(self.positions, self.angles):
            tf = Affine2D().rotate(angle).translate(*position)
            patches.append(PathPatch(path.transformed(tf)))
        result.append((patches, self.colors))

        return result
Example #27
0
def visualize_min_sum_sol_2d(solution: 'AngularGraphSolution'):
    graph = solution.graph
    fig = plt.figure()
    axis = plt.subplot()
    axis.axis('off')

    _visualize_edges_2d(solution.graph)
    _visualize_vertices_2d(solution.graph)
    _visualize_celest_body_2d(axis, solution.graph)

    # Make an edge order for vertices
    vertex_order = Multidict()
    ordered_times = solution.get_ordered_times()
    for time_key in ordered_times.get_ordered_keys():
        for edges in ordered_times[time_key]:
            if edges[0] < edges[1]:
                vertex_order[edges[0]] = edges
                vertex_order[edges[1]] = edges
    # Get minimum edge length
    min_length = max(
        np.array([
            np.linalg.norm(solution.graph.vertices[i] -
                           solution.graph.vertices[j])
            for i, j in solution.graph.edges
        ]).min(), 0.4)
    # Draws the angle paths in a circular fashion
    path_list = []
    last_points = []
    for vertex_key in vertex_order:
        last_edge = None
        last_direction = None
        current_min_length = min_length * 0.3
        last_point = None
        for edge in vertex_order[vertex_key]:
            if last_edge:
                other_vertices = np.hstack([
                    np.setdiff1d(np.array(last_edge), np.array([vertex_key])),
                    np.setdiff1d(np.array(edge), np.array([vertex_key]))
                ])

                angles = [
                    get_angle(graph.vertices[vertex_key],
                              graph.vertices[vertex_key] + [1, 0],
                              graph.vertices[other_vertex])
                    for other_vertex in other_vertices
                ]

                # If y-coord is below the current vertex we need to calculate the angle different
                for i in range(len(angles)):
                    if graph.vertices[other_vertices[i]][1] < graph.vertices[
                            vertex_key][1]:
                        angles[i] = 360 - angles[i]

                # Calculate if we need to go from angle[0] to angle[1] or other way around
                # to not create an arc over 180 degrees
                diff = abs(angles[0] - angles[1])
                if diff > 180:
                    diff = 360 - diff
                normal_angle_direction = math.isclose((angles[0] + diff) % 360,
                                                      angles[1],
                                                      rel_tol=1e-5)
                if not normal_angle_direction:
                    angles = reversed(angles)

                # 1 shall be clockwise and -1 counter-clockwise direction
                current_direction = 1 if normal_angle_direction else -1

                if last_direction:
                    if current_direction != last_direction:  # direction change happened
                        current_min_length *= 1.25

                # Transform the arc to the right position
                transform = mtransforms.Affine2D().scale(
                    current_min_length, current_min_length)
                transform = transform.translate(*graph.vertices[vertex_key])
                arc = Path.arc(*angles)
                arc_t = arc.transformed(transform)

                if last_direction:
                    if current_direction != last_direction:  # direction change happened
                        last_vertex = path_list[-1].vertices[
                            -1] if last_direction == 1 else path_list[
                                -1].vertices[0]
                        new_vertex = arc_t.vertices[
                            0] if current_direction == 1 else arc_t.vertices[-1]
                        bridge_path = Path([last_vertex, new_vertex])
                        path_list.append(bridge_path)

                last_direction = current_direction
                path_list.append(arc_t)
                last_point = path_list[-1].vertices[
                    -1] if last_direction == 1 else path_list[-1].vertices[0]
                last_points.append(last_point)
            last_edge = edge
        # Add these points to detect direction
        last_points.append(last_point)

    path_collection = PathCollection(path_list,
                                     edgecolor='r',
                                     facecolor='#00000000')
    axis.add_collection(path_collection)
    a_last_points = np.array([l for l in last_points if l is not None])
    plt.plot(a_last_points[:, 0], a_last_points[:, 1], 'r.')
    axis.autoscale()
    plt.show()
Example #28
0
def arc_path(start, radius, rotation, large, sweep, end):
    """
    Generate an elliptical arc path given an endpoint parameterization.

    Uses matplotlib to draw the arc using quadratic Bezier curves.

    Parameters
    ----------
    start : complex
        Starting point (x1, y1).
    radius : complex
        Two elliptical radii (rx, ry).
    rotation : float
        Angle from the x-axis of the current coordinate system to the x-axis of
        the ellipse.
    large : bool
        False if an arc spanning < 180 degrees is to be drawn, True if an arc
        spanning >= 180 degrees is to be drawn.
    sweep : bool
        If sweep-flag is True, then the arc will be drawn in a "positive-angle"
        direction from start to end.
    end : complex
        End point (x2, y2).

    Returns
    -------
    codes : array (n,)
        Command codes
    verts : array (n,2)
        Vertices

    Notes
    -----
    We first perform a conversion to center parameterization and generate
    a circular arc at the origin. Then we apply scaling, translation and
    rotation.

    One can think of an ellipse as a circle that has been stretched and then
    rotated. Start by making an arc along the unit circle from `theta1` to
    `theta2`, centered at `center`. Then scale the circle along the x and y
    axes according to the given radii. Finally, rotate the arc around the
    center through the given angle `rotation`.

    """
    radius, center, theta1, theta2 = endpoint_to_center(
        start, radius, rotation, large, sweep, end)

    # Create an arc on the unit circle
    # Matplotlib does this using CURVE4 operations
    # https://matplotlib.org/stable/_modules/matplotlib/path.html#Path.arc
    if theta2 > theta1:
        arc = Path.arc(theta1=theta1, theta2=theta2)
        reverse_path = False
    else:
        arc = Path.arc(theta1=theta2, theta2=theta1)
        reverse_path = True

    # Transform it into an elliptical arc:
    # * scale the minor and major axes
    # * translate it to the center
    # * rotate the x-axis of the ellipse from the x-axis of the current
    #   coordinate system
    trans = (transforms.Affine2D().scale(radius.real, radius.imag).translate(
        center.real, center.imag).rotate_deg_around(center.real, center.imag,
                                                    rotation))
    arc = trans.transform_path(arc)
    codes = np.array(arc.codes)
    verts = np.array(arc.vertices)

    # Make sure we are drawing from start to end
    if reverse_path:
        verts = verts[::-1, :]

    # Change the initial MOVETO operation into a LINETO to connect to the
    # previous path
    codes[0] = Path.LINETO

    return codes, verts
Example #29
0
def path_arc_smart(theta1, theta2):
    if theta1 >= theta2:
        return path_arc_cw(theta1, theta2)
    else:
        return Path.arc(theta1, theta2)