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