예제 #1
0
 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()
예제 #2
0
    def __init__(self, obj):
        self.obj = obj
        self.parameters = Parameters(self.obj)
        self.wavy = False
        self.waviness = None
        self.options = Options()
        self.draw_bounding_box = False

        self.already_computed_BB = {}
        self.already_computed_math_BB = {}

        self.record_add_to_bb = []
        self.separator_name = "DEFAULT"
        self.in_math_bounding_box = True
        self.in_bounding_box = True
        self._draw_edges = False
        self.added_objects = AddedObjects()

        self.take_BB = True
        self.take_math_BB = True

        self.mark = None
        self.marque = None
예제 #3
0
    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
예제 #4
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)
 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()
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)
예제 #7
0
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)
예제 #8
0
class ObjectGraph:
    """
    This class is supposed to be used to create other "<Foo>Graph"
    by inheritance.

    Objects that are going to be drawn have to derive from `ObjectGraph`.
    When creating you own class,
    - do not override `bounding_box` nor `math_bounding_box`
    - create the functions `_bounding_box`
      and eventually `_math_bounding_box`
    - consider the attributes `take_BB` and `take_math_BB`.
    """
    def __init__(self, obj):
        self.obj = obj
        self.parameters = Parameters(self.obj)
        self.wavy = False
        self.waviness = None
        self.options = Options()
        self.draw_bounding_box = False

        self.already_computed_BB = {}
        self.already_computed_math_BB = {}

        self.record_add_to_bb = []
        self.separator_name = "DEFAULT"
        self.in_math_bounding_box = True
        self.in_bounding_box = True
        self._draw_edges = False
        self.added_objects = AddedObjects()

        self.take_BB = True
        self.take_math_BB = True

        self.mark = None
        self.marque = None

    def draw_edges(self):
        self._draw_edges = True

    def wave(self, dx, dy):
        """
        Make self wavy.

        dx is the wave length and dy is the amplitude
        """
        from yanntricks.src.parameters.Waviness import Waviness
        self.wavy = True
        self.waviness = Waviness(self, dx, dy)

    def get_arrow(self, llam):
        """
        return a small arrow at position 'llam'.

        This only works if one has a 'get_tangent_vector' method.

        - `llam` could be radian, degree or something else,
                 depending on the actual object on which
                 you are using this.
        """
        try:
            v = self.get_tangent_vector(llam)
        except AttributeError:
            print("you are using 'get_arrow' (probably from a "
                  "'put_arrow' on your part) on an object that "
                  "does not support 'get_tangent_vector'")
        v = v.normalize(0.01)
        return v

    def get_mark(self,
                 dist,
                 angle=None,
                 text=None,
                 mark_point=None,
                 added_angle=None,
                 position=None,
                 pspict=None):
        # pylint:disable=too-many-branches
        """
        - `angle` is degree or AngleMeasure

        In the internal representation of the mark,
        the angle type will be `AngleMeasure`
        """
        from yanntricks.src.Constructors import Mark
        from yanntricks.src.AngleMeasure import AngleMeasure

        self.marque = True

        if position in ["N", "S", "E", "W"] and angle is not None:
            angle = None
            logging(
                f"When you want a position like N,S,E, or W, "
                f"the mark angle should not be given.",
                pspict=pspict)

        if angle is None and position not in ["N", "S", "E", "W"]:
            try:
                angle = self.advised_mark_angle(pspict=pspict)
            except AttributeError:
                angle = self.angle().degree + 90

        if position == "center_direction":
            # In this case we assume 'self' is a point
            angle = angle.degree

        if isinstance(angle, AngleMeasure):
            angle = angle.degree

        # At this point, 'angle' has to be degree,
        # - for the possibility of "added_angle"
        # - the constructor of 'mark' expects degree or AngleMeasure

        if added_angle:
            angle = angle + added_angle
        if position is None:
            position = "corner"
            alpha = AngleMeasure(value_degree=angle).positive()
            deg = alpha.degree
            if deg == 0:
                position = "W"
            if deg == 90:
                position = "S"
            if deg == 180:
                position = "E"
            if deg == 180 + 90:
                position = "N"

        if position in ["N", "S", "E", "W"]:
            angle = None

        mark = Mark(graph=self,
                    dist=dist,
                    angle=angle,
                    central_point=None,
                    text=text,
                    mark_point=mark_point,
                    position=position,
                    pspict=pspict)

        # In each .psttricks file we need the lines that make compute
        # the size of the text. Thus we call "get_box_size" for each.
        if not isinstance(pspict, list):
            pspict = [pspict]
        for psp in pspict:
            _, _ = psp.get_box_size(text)

        return mark

    def put_mark(self,
                 dist=None,
                 angle=None,
                 text="",
                 mark_point=None,
                 added_angle=None,
                 position=None,
                 pspict=None,
                 pspicts=None):
        """
        Put a mark on an object


        If you want to put a mark on an object
        P.put_mark(0.1,text="foobar",pspict=pspict,position="N")

        mark_point is a function which returns the
        position of the mark point.

        If you give no position (i.e. no "S","N", etc.) the position will
        be automatic regarding the angle.

        - ``angle`` is given in degree.
        set `position` to "center" is dangerous because
        it puts the center of the box at given angle and distance.
        Thus the text can be ill placed, especially if the given
        `dist` is lower than the half of the box size.

        `center_direction` : the mark is placed in such a way
        that the center is at given angle, and the box's border
        at given distance.
        """
        pspicts = make_psp_list(pspict, pspicts)

        for psp in pspicts:
            mark = self.get_mark(dist,
                                 angle,
                                 text,
                                 mark_point=mark_point,
                                 added_angle=added_angle,
                                 position=position,
                                 pspict=psp)

            if position in ["N", "S", "E", "W"] and angle is not None:
                logging("When you want a position like N,S,E, or W,\
                        the mark angle should not be given.")
            self.added_objects.append(psp, mark)

        self.mark = mark

    def add_option(self, opt):
        self.options.add_option(opt)

    def get_option(self, opt):
        return self.options.DicoOptions[opt]

    def remove_option(self, opt):
        self.options.remove_option(opt)

    def merge_options(self, graph):
        """
        Take an other object <Foo>Graph and merges
        the options as explained in the documentation
        of the class Options.
        That merge takes into account the
        attributes "color", "style", wavy
        """
        self.parameters = graph.parameters
        self.options.merge_options(graph.options)
        self.wavy = graph.wavy
        self.waviness = graph.waviness

    def conclude_params(self):
        """
        However the better way to add something like
        linewidth=1mm
        to a graph object `seg` is to use
        seg.add_option("linewidth=1mm")
        """
        oo = self.parameters.other_options
        for opt in oo:
            self.add_option(opt + "=" + oo[opt])
        self.parameters.add_to_options(self.options)

    def params(self, language, refute=None):
        refute = refute or []
        return self.bracketAttributesText(language=language, refute=refute)

    def bracketAttributesText(self, language, refute=None):
        from yanntricks.src.BasicGeometricObjects import genericBracketAttributeToLanguage

        refute = refute or []

        self.conclude_params()

        # Create the text  a1=va,a2=v2, etc.
        # 1935811332
        l = []
        bracket_attributes = self.parameters.bracketAttributesDictionary()
        for attr in [x for x in bracket_attributes if x not in refute]:
            value = bracket_attributes[attr]
            l_attr = genericBracketAttributeToLanguage(attr, language)
            if value is not None:
                if attr == "linewidth":
                    l.append(l_attr + "=" + str(value) + "pt")
                else:
                    l.append(l_attr + "=" + str(value))
        code = ",".join(l)
        return code

    def action_on_pspict(self, pspict):
        pass

    def conclude(self, pspict):
        """
        The `conclude` function allows an object to make its ultimate
        settings before to be drawn.
        This is used for objects like axes
        that have a list of added objects (the graduation bars)
        that can depend on the other objects in the picture.
        """

    def _draw_added_objects(self, pspict):
        # position 3598-30738
        for obj in self.added_objects[pspict]:
            pspict.DrawGraphs(obj)

    # We could be tempted to furnish here a default
    # '_bounding_box(self,pspict)'
    # Indeed, some are uniquely build from 'action_on_pspict', so that the
    # bounding box is not important to know since the building block
    # have theirs.
    def bounding_box(self, pspict=None):
        try:
            return self.already_computed_BB[pspict]
        except KeyError:
            pass
        bb = self._bounding_box(pspict)
        self.already_computed_BB[pspict] = bb
        return bb

    def math_bounding_box(self, pspict):
        try:
            return self.already_computed_math_BB[pspict]
        except KeyError:
            pass
        try:
            bb = self._math_bounding_box(pspict)
        except AttributeError:
            bb = self.bounding_box(pspict=pspict)
        bb.is_math = True
        self.already_computed_math_BB[pspict] = bb
        return bb

    def latex_code(self, pspict, language=None):
        # pylint: disable=no-self-use
        # pylint: disable=unused-argument
        # This method must be overridden.
        return ""

    def _bounding_box(self, pspict):
        # pylint: disable=no-self-use
        # pylint: disable=unused-argument
        # This method must be overridden.
        return ""

    def angle(self):
        # pylint: disable=no-self-use
        # This method must be overridden.
        return None