class CustomSurface(ObjectGraph):
    """
    INPUT:

    - args - A list or a tuple of graphs that can compose a \pscustom
    """
    def __init__(self, args):
        ObjectGraph.__init__(self, self)
        # self.add_option("fillstyle=vlines,linestyle=none")
        self.add_option("fillstyle=none,linestyle=none")
        self.graphList = args
        self.edges = Parameters()

    def _bounding_box(self, pspict=None):
        bb = BoundingBox()
        for obj in self.graphList:
            bb.AddBB(obj.bounding_box(pspict))
        return bb

    def _math_bounding_box(self, pspict=None):
        bb = BoundingBox()
        for obj in self.graphList:
            bb.AddBB(obj.math_bounding_box(pspict=pspict))
        return bb

    def tikz_code(self, pspict=None):
        """
        If the CustomSurface has to be filled, we start by plotting the filling.

        Then we plot, separately, the lines making the border. Thus we can have different colors and line style for the different edges.
        """
        # The color attribution priority is the following.
        # if self.parameters.color is given, then this will be the color
        # if an hatch or a fill color is given and no self.parameters.color, then this will be used
        a = []
        color = None

        # It cannot be filled by default when a color is given because Rectangles and Polygon drop here
        # if self.parameters.color :
        #    if self.parameters._hatched==False:     # By default it will be filled if one give a color
        #        self.parameters._filled=True

        if self.parameters._filled and self.parameters.fill.color:
            color = self.parameters.fill.color
        if self.parameters._hatched and self.parameters.hatch.color:
            color = self.parameters.hatch.color
        if self.parameters._filled or self.parameters._hatched:
            l = []
            for obj in self.graphList:
                try:
                    l.extend([
                        p.coordinates(digits=5, pspict=pspict)
                        for p in obj.representative_points()
                    ])
                except AttributeError:
                    print("The object " + obj +
                          " seems to have no 'representative_points' method")
                    raise
                    obj_code = obj.latex_code(language="tikz", pspict=pspict)
                    l.append(draw_to_fill(obj_code))
            l.append(" cycle;")
            code = " -- ".join(l)
            if self.parameters._hatched:
                # This is from
                # http://www.techques.com/question/31-54358/custom-and-built-in-tikz-fill-patterns
                # position 170321508
                def_hatching = r"""
% declaring the keys in tikz
\tikzset{hatchspread/.code={\setlength{\hatchspread}{#1}},
         hatchthickness/.code={\setlength{\hatchthickness}{#1}}}
% setting the default values
\tikzset{hatchspread=3pt,
         hatchthickness=0.4pt}
"""
                a.append(def_hatching)
                if color == None:
                    color = "lightgray"
                options = "color=" + color
                options = options + ",  pattern=custom north west lines,hatchspread=10pt,hatchthickness=1pt "
            if self.parameters._filled:
                options = "color=" + color
            a.append("\\fill [{}] ".format(options) + code)

        return "\n".join(a)

    def latex_code(self, language=None, pspict=None):
        """
        There are two quite different ways to get here. The first is to ask a surface under a function and the second is to ask for a rectangle or a polygon.

        if self.parameters.color is given, this will be the color of the edges

        If one wants to give different colors to edges, one has to ask explicitly using
        self.Isegment
        self.Fsegment
        self.curve1
        self.curve2
        in the case of surface between curves.

        If one wants the surface to be filled or hatched, on has to ask explicitly.
        """
        a = []
        if language == "tikz":
            a.append(self.tikz_code(pspict))
        if self._draw_edges:
            for obj in self.graphList:
                obj.parameters = self.edges.copy()
                a.append(obj.latex_code(language=language, pspict=pspict))
        return '\n'.join(a)
Beispiel #2
0
class GeometricImplicitCurve(object):
    """
    Describe a curve given by an implicit equation.

    INPUT:

    - ``f`` -- a function of two variables or equation in two variables

    End users should not use this class but use the constrcutor :func:`ImplicitCurve`.

    EXAMPLES::

        sage: from yanntricks.BasicGeometricObjects import *
        sage: x,y=var('x,y')
        sage: f(x,y)=x**2+1/x
        sage: F=GeometricImplicitCurve(f(x,y)==2)
        sage: G=GeometricImplicitCurve(x+y==2)   

    """
    def __init__(self, f):
        self.f = f
        self.parameters = Parameters()
        from sage.symbolic.expression import is_SymbolicEquation
        if is_SymbolicEquation(f):
            if f.operator() != operator.eq:
                raise ValueError(
                    "input to implicit plot must be function or equation")
            # At this point self.f is the expression to be equated to zero.
            self.f = f.lhs() - f.rhs()

    def graph(self, xrange, yrange, plot_points=100):
        """
        Return the graph corresponding to the implicit curve.

        INPUT:

        - ``xrange`` - the X-range on which the curve will be plotted.

        - ``yrange`` - the Y-range on which the curve will be plotted.

        EXAMPLE ::
    
            sage: from yanntricks.BasicGeometricObjects import *
            sage: x,y=var('x,y')
            sage: F=GeometricImplicitCurve(x-y==3)
            sage: graph=F.graph((x,-3,3),(y,-2,2))
            sage: print graph.bounding_box()
            <BoundingBox xmin=1.0,xmax=3.0; ymin=-2.0,ymax=0.0>

        """
        gr = ImplicitCurveGraph(self, xrange, yrange, plot_points)
        gr.parameters = self.parameters.copy()
        return gr

    def __str__(self):
        """
        Return string representation of this implicit curve

        EXAMPLE::

            sage: from yanntricks.BasicGeometricObjects import *
            sage: x,y=var('x,y')
            sage: f(x,y)=x**2+1/x
            sage: print GeometricImplicitCurve(f(x,y)==2)
            <Implicit curve of equation x^2 + 1/x - 2 == 0>
            sage: print GeometricImplicitCurve(x+y==7)   
            <Implicit curve of equation x + y - 7 == 0>
        """
        return "<Implicit curve of equation %s == 0>" % repr(self.f)
class PolygonGraph(ObjectGraph):
    def __init__(self, points_list):
        ObjectGraph.__init__(self, self)
        self.edges = []
        self.vertices = points_list
        self.points_list = self.vertices

        for i in range(0, len(self.points_list)):
            segment = Segment(
                self.points_list[i],
                self.points_list[(i + 1) % len(self.points_list)])
            self.edges.append(segment)
        self.draw_edges = True
        self.independent_edge = False
        self.parameters = None

        from yanntricks.src.parameters.Parameters import Parameters
        from yanntricks.src.parameters.HatchParameters import HatchParameters
        from yanntricks.src.parameters.FillParameters import FillParameters

        self.edges_parameters = Parameters(self)
        self.hatch_parameters = HatchParameters()
        self.fill_parameters = FillParameters()
        self._hatched = False
        self._filled = False

    def hatched(self):
        self._hatched = True

    def filled(self):
        self._filled = True

    def rotation(self, angle):
        from yanntricks.src.Constructors import Polygon
        pts = [P.rotation(angle) for P in self.points_list]
        return Polygon(pts)

    def make_edges_independent(self):
        """
        make the edges customisation independent the one to the other.
        """
        self.independent_edge = True

    def no_edges(self):
        """
        When X.no_edges() is used, the edges of the polygon will not be drawn.

        The argument `points_name` override `text_list`.
        """
        self.draw_edges = False

    def put_mark(self,
                 dist,
                 text_list=None,
                 points_names=None,
                 mark_point=None,
                 pspict=None,
                 pspicts=None):
        from yanntricks.src.affine_vector import AffineVector
        from yanntricks.src.Visual import visual_vector
        from yanntricks.src.Visual import polar_to_visual_polar

        pspicts = make_psp_list(pspict, pspicts)

        n = len(self.points_list)
        if not text_list and not points_names:
            text_list = [
                "\({}\)".format(x) for x in string.ascii_uppercase[0:n]
            ]
        # One can do :
        # polygon.put_mark(0.2,points_names=" B ",pspict=pspict)
        # or
        # polygon.put_mark(0.3,text_list=["","\( F\)","\( E\)"],pspict=pspict)
        # where 'polygon' is a triangle.
        # In both cases we have an empty string as mark and then a box
        # of size zero.
        # We thus have to following error
        # TypeError: cannot evaluate symbolic expression numerically
        # on the second pass, because the size of the box being zero,
        # the line equations somehow trivializes themselves and Sage
        # founds infinitely many intersections.
        # This is why we do not something like :
        # text_list=[ "\( {} \)".format(x) for x in points_names ]
        if text_list:
            for i in range(len(text_list)):
                if text_list[i] == "":
                    text_list[i] = None
        if points_names:
            text_list = []
            for x in points_names:
                if x == " ":
                    text_list.append(None)
                else:
                    text_list.append("\( {} \)".format(x))
        for i, P in enumerate(self.points_list):
            text = text_list[i]
            if text is not None:
                A = self.points_list[(i - 1) % n]
                B = self.points_list[(i + 1) % n]
                v1 = AffineVector(A, P).fix_origin(P).normalize(1)
                v2 = AffineVector(B, P).fix_origin(P).normalize(1)

                # 'direction' is a vector based at 'P' that points
                # in the direction as external to the angle as possible.
                # This is the "external" angle bisector
                # Why not `direction=v1+v2` ? Because we are victim
                # of the problem described
                # in the comment of AffineVectorGraph.__add__
                direction = v1.extend(v2)
                angle = direction.angle()
                for psp in pspicts:
                    P.put_mark(dist=dist,
                               angle=angle,
                               added_angle=0,
                               text=text,
                               position="center_direction",
                               pspict=psp)
                    self.added_objects.append(psp, P)

    def _math_bounding_box(self, pspict=None):
        bb = BoundingBox()
        bb.is_math = True
        for P in self.points_list:
            bb.append(P, pspict=pspict)
        return bb

    def _bounding_box(self, pspict=None):
        return self.math_bounding_box(pspict)

    def action_on_pspict(self, pspict):
        """If one wants to fill or hatch, one has to ask explicitly."""
        from yanntricks.src.CustomSurfaceGraph import CustomSurface
        if self._filled:
            custom = CustomSurface(self.edges)
            custom.parameters.filled()
            custom.parameters.fill = self.fill_parameters.copy()
            pspict.DrawGraphs(custom)
        if self._hatched:
            custom = CustomSurface(self.edges)
            custom.parameters.hatched()
            custom.parameters.hatch = self.hatch_parameters.copy()
            pspict.DrawGraphs(custom)
        if self.draw_edges:
            for edge in self.edges:
                if not self.independent_edge:
                    edge.parameters = self.edges_parameters.copy()
                pspict.DrawGraphs(edge)