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 __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 __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
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)
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)
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