def drawArrow(x, y, w, hw, hh): global d d.append(svg.Lines(x, y, x + w - hw / 2, y, stroke="black")) d.append( svg.Lines(x + w - hw, y - hh / 2, x + w, y, x + w - hw, y + hh / 2, close=True, fill="black"))
def draw(self, x=0, y=0, xscale=1.0): h = self.h a = self.a * xscale b = self.b * xscale x = x * xscale #assert isinstance(x, float) and isinstance(y, float) d = draw.Group(transform="translate({} {})".format(x, y)) d.append( draw.Rectangle(a, 0, b - a, h, fill=self.color, stroke=self.color)) if 'f' in self.direction: d.append( draw.Lines(b, 0, b + 5, (h / 2), b, h, fill=self.color, stroke=self.color)) if 'r' in self.direction: d.append( draw.Lines(a, 0, a - 5, (h / 2), a, h, fill=self.color, stroke=self.color)) for r_a, r_b, color in self.regions: r_a = r_a * xscale r_b = r_b * xscale d.append( draw.Rectangle(r_a, 0, r_b - r_a, h, fill=color, stroke=color)) for tick in self.ticks: tick = tick * xscale d.append(draw.Lines(tick, 0, tick, h, stroke='red')) if self.label: label = self.label font_size = 10 offset = h + font_size if isinstance(self.label, Label): d.append(label.draw(x=(b + a) / 2)) elif isinstance(self.label, str): d.append(Label(0, self.label).draw(x=(b + a) / 2)) return d
def make_arrow(self, color): key = color if key in self.arrows: return self.arrows[key] arrow = draw.Marker(-0.1, -0.5, 1.1, 0.5, scale=4) arrow.append(draw.Lines(1, -0.5, 1, 0.5, 0, 0, fill=color, close=True)) self.arrows[key] = arrow return arrow
def draw(self, x=0, y=0, xscale=1.0): d = draw.Group(transform="translate({} {})".format(x, y)) d.append(self.t1.draw(xscale=xscale)) d.append(self.t2.draw(y=self.t1.h + self.gap, xscale=xscale)) for bottom, top in self.connections: bottom = bottom * xscale top = top * xscale d.append(draw.Lines(bottom, 0, top, -self.gap, stroke=self.color)) d.append( draw.Lines(bottom, self.t1.h, bottom, 0, stroke=self.color)) d.append( draw.Lines(top, -self.gap, top, -(self.gap + self.t2.h), stroke=self.color)) return d
def lines(latstep, longstep, width, d, latorlong): if (latorlong is "long"): for long in range(-180, 181, longstep): points = [] for lat in range(-90, 91, latstep): (x, y) = projection.robinson(lat, long, width / 6.1) points.append(x) points.append(y) d.append(draw.Lines(*points, close=False, fill='', stroke='black')) elif latorlong is "lat": for lat in range(-90, 91, latstep): points = [] for long in range(-180, 181, longstep): (x, y) = projection.robinson(lat, long, width / 6.1) points.append(x) points.append(y) d.append(draw.Lines(*points, close=False, fill='', stroke='black')) return d
def draw(self, x=0, y=0, xscale=1.0): h = self.h #assert isinstance(x, int) and isinstance(y, int) g = draw.Group(transform="translate({} {})".format(x, y)) if self.join: start = min([t.a for t in self.tracks]) * xscale end = max([t.b for t in self.tracks]) * xscale g.append(draw.Lines(start, h / 2, end, h / 2, stroke='lightgrey')) for track in self.tracks: g.append(track.draw(xscale=xscale)) return g
def __init__(self, is_gem_sbml=False, pathway_id='rp_pathway', central_species_group_id='central_species', sink_species_group_id='rp_sink_species', model_name=None, document=None, path=None, rpcache=None): """Class constructor .. document private functions .. automethod:: hierarchyPos """ super().__init__(is_gem_sbml, pathway_id, central_species_group_id, sink_species_group_id, model_name, document, path, rpcache) #self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(os.path.basename(__file__)) self.mnx_cofactors = json.load(open('data/mnx_cofactors.json', 'r')) #some drawing constants self.arrowhead = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=4, orient='auto', id='normal_arrow') self.arrowhead.append(draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill='black', close=True)) self.arrowhead_flat = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=4, orient=0, id='flat_arrow') self.arrowhead_flat.append(draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill='black', close=True)) self.rev_arrowhead_flat = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=4, orient=180, id='rev_flat_arrow') self.rev_arrowhead_flat.append(draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill='black', close=True)) ''' self.rev_arrowhead = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=4, orient=0, id='rev_flat_arrow') self.rev_arrowhead.append(draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill='black', close=True)) ''' self.arrowhead_comp_x = 7.0 self.arrowhead_comp_y = 7.0
def drawCube(x, y, w, h, z): global d d.append( svg.Lines(x, y, x + w, y, x + w, y + h, x, y + h, close=True, fill="white", stroke="black")) d.append( svg.Lines(x + w, y, x + w + z, y + z, x + w + z, y + h + z, x + w, y + h, close=True, fill="#555555", stroke="black")) d.append( svg.Lines(x, y + h, x + w, y + h, x + w + z, y + h + z, x + z, y + h + z, close=True, fill="#cccccc", stroke="black"))
def drawRec( self, d, x1, y1, x2, y2, fill="f", pen=5, ): # 'f'->filled shape in background color # 'F' -> filled shape in pen color # 'N' -> unfilled shape # pen->stroke_width pen = int(pen) x1 = int(x1) y1 = int(y1) x2 = int(x2) y2 = int(y2) # self.update_svg_boundary(x1,'x') # self.update_svg_boundary(y1,'y') # self.update_svg_boundary(x2,'x') # self.update_svg_boundary(y2,'y') if fill == "f": kwargs = {"fill_opacity": 0} elif fill == "F": kwargs = {"fill": self.STROKE_COLOR} else: kwargs = {"fill_opacity": 0} d.append( draw.Lines(x1, y1, x2, y1, x2, y2, x1, y2, x1, y1, stroke_width=pen, stroke=self.STROKE_COLOR, **kwargs)) self.update_svg_boundary([(x1, y1), (x2, y2)]) return d
def draw_svg_polygon(canvas, polygon, BGR_color, alpha): """ Draws an irregular polygon to a canvas :param canvas: the svg drawing canvas :param polygon: the polygon points :param BGR_color: BGR color tuple of values 0 => 255 :param alpha: opacity 0 -> 1 opacity """ points = polygon.ravel().tolist() canvas.append( draw.Lines( *points, close=False, fill=rgb2hex(*BGR_color[::-1]), fill_opacity=alpha, ))
def draw(self, x=0, y=0, xscale=1.0): h = self.h a = self.x * xscale b = (self.x + self.w) * xscale x = x * xscale r = h / 2 font_size = h * 0.55 arrow_size = 7 if self.direction >= 0: line_start = a arrow_end = b arrow_start = max(arrow_end - arrow_size, line_start) else: line_start = b arrow_end = a arrow_start = min(arrow_end + arrow_size, line_start) centre = (a + b) / 2 arrow_y = h / 2 + self.elevation * r group = draw.Group(transform="translate({} {})".format(x, y)) group.append( draw.Line(line_start, arrow_y, arrow_start, arrow_y, stroke='black')) group.append( draw.Circle(centre, h / 2, r, fill='ivory', stroke='black')) group.append( draw.Lines(arrow_end, arrow_y, arrow_start, arrow_y + arrow_size / 2, arrow_start, arrow_y - arrow_size / 2, arrow_end, arrow_y, fill='black')) group.append( draw.Text(self.label, font_size, centre, h / 2, text_anchor='middle', dy="0.35em")) return group
def build(self, offset, technique, height, width, tBC, subtechniques=[], mode=(True, False), tactic=None, colors=[]): """ Build a SVG Technique block :param offset: Current offset to build the block at (so it fits in the column) :param technique: The technique to build a block for :param height: The height of the technique block :param width: The width of the technique block :param tBC: The hex code of the technique block's border :param subtechniques: List of any visible subtechniques for this technique :param mode: Display mode (Show Name, Show ID) :param tactic: The corresponding tactic :param colors: List of all default color values if no score can be found :return: The newly created SVG technique block """ g = G(ty=offset) c = self._com_color(technique, tactic, colors) t = dict(name=self._disp(technique.name, technique.id, mode), id=technique.id, color=tuple(int(c[i:i+2], 16) for i in (0, 2, 4))) tech, text = self._block(t, height, width, tBC=tBC) g.append(tech) g.append(text) new_offset = height for entry in subtechniques: gp = G(tx=width/5, ty=new_offset) g.append(gp) c = self._com_color(entry, tactic, colors) st = dict(name=self._disp(entry.name, entry.id, mode), id=entry.id, color=tuple(int(c[i:i + 2], 16) for i in (0, 2, 4))) subtech, subtext = self._block(st, height, width - width/5, tBC=tBC) gp.append(subtech) gp.append(subtext) new_offset = new_offset + height if len(subtechniques): g.append(draw.Lines(width/16, -height, width/8, -height * 2, width/8, -height * (len(subtechniques) + 1), width/5, -height * (len(subtechniques) + 1), width/5, -height, close=True, fill=tBC, stroke=tBC)) return g, offset + new_offset
def create_image_file(fieldname, profile_dict, datafolder=input_data_folder, imgsize=100, isOpenClose=True): d = draw.Drawing(imgsize, imgsize, origin='center') profilepoints = [] for tpl in profile_dict[fieldname]: profilepoints.append(tpl[0]) profilepoints.append(tpl[1]) d.append( draw.Lines(profilepoints[0], profilepoints[1], *profilepoints, close=isOpenClose, fill='none', stroke='black')) shape = profile_dict['ShapeName'] # d.saveSvg(datafolder+"/"+shape+'.svg') d.savePng(datafolder + "/" + shape + '_' + fieldname + '.png')
def drawSvg(w, h, polygons, word, title, saveto_path): w = 183 h = 309 mult = 4 bleed = 72 framemargin = 10 margin = framemargin + bleed titlemargin = 96 width = w * mult + 2 * margin height = h * mult + 2 * margin + titlemargin d = draw.Drawing( width, height, origin=(0, 0 - titlemargin), style= "font-size:70;font-family:Poor Richard, Times New Roman;stroke:black;stroke-width:1;fill:none" ) #d.append(draw.Rectangle(margin-framemargin, margin-framemargin, \ # w * mult + 2 * framemargin, h * mult + 2 * framemargin, \ # stroke='black', stroke_width=4, fill='none')) for polygon in polygons: color = polygon['color'] hex = '#%02x%02x%02x' % (color[0], color[1], color[2]) opacity = color[3] / 255 shape = polygon['shape'] # hard-coded for 6-polygons p = draw.Lines(shape[0][0] * mult + margin, shape[0][1] * mult + margin, shape[1][0] * mult + margin, shape[1][1] * mult + margin, \ shape[2][0] * mult + margin, shape[2][1] * mult + margin, \ shape[3][0] * mult + margin, shape[3][1] * mult + margin, \ shape[4][0] * mult + margin, shape[4][1] * mult + margin, \ shape[5][0] * mult + margin, shape[5][1] * mult + margin, \ stroke_width=0, close=True, fill=hex, fill_opacity=opacity) d.append(p) d.append(draw.Text(title.upper(), 70, width/2, 0, \ center=0.5, fill='black')) d.saveSvg(saveto_path)
def draw_hours(d, show_lines=True): for hour in range(10, 23): suffix = 'AM' if hour < 12 else 'PM' prefix = hour if hour <= 12 else hour - 12 hour_string = '{} {}'.format(prefix, suffix) x_pos = TOTAL_WIDTH - RIGHT_BUFFER + 3 y_pos = COL_HEIGHT - (hour - 10) * HOUR_HEIGHT t = draw.Text(hour_string, 10, x_pos, y_pos - 3, center=0, fill='black') d.append(t) if show_lines: l = draw.Lines(0, y_pos, TOTAL_WIDTH - RIGHT_BUFFER, y_pos, close=False, stroke='#444', stroke_width=0.5) d.append(l)
def drawPolygon( self, d, vertices_count, pen=5, vertices_list=[], fill="f", ): pen = int(pen) # only fill the polygon if its implicitly closed # # unpack arguments in list using *operator # kwargs = {} if fill == 'F': # fill the shape kwargs["fill"] = self.STROKE_COLOR if fill == 'f' or fill == 'N': # dont fill the shape kwargs["fill_opacity"] = 0 arg = [] for i in range(0, len(vertices_list)): for index in range(0, 2): arg.append(int(vertices_list[i][index])) self.update_svg_boundary(vertices_list) d.append( draw.Lines(*arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, **kwargs)) return d
def save(self, filename, open_viewer=False): svg_canvas = drawSvg.Drawing(self.width, self.height, origin=(0, 0), displayInline=False) # There may be a better way to do this through the init above, but I found it confusing. # it was much easier to just hardcode it. svg_canvas.viewBox = (0, 0, self.width, self.height) if self.units: svg_canvas.width = f'{self.width}{self.units}' svg_canvas.height = f'{self.height}{self.units}' if self.background_color: svg_canvas.append( drawSvg.Rectangle(x=0, y=0, width='100%', height='100%', fill=self.background_color)) # reversed is in here to show/write the objects in order they were added to the queue. # This should better reflect the serial way objects were created. for shape in reversed(self.draw_queue): if shape.is_circle: # the lib wants to always make Y coods negative. This is likely because of the assumption # the moving physically down on the Y axis puts an object in the correct region. # Basically, negative Y is 'viewable' where as in P5, positive Y is viewable. svg_obj = drawSvg.Ellipse(shape.x, shape.y * -1, shape.radius_x, shape.radius_y, fill=shape.fill_color, stroke=shape.stroke_color, stroke_width=shape.stroke_width) elif len(shape.vertices) > 0: verts = shape.vertices # verts = vertices.tolist() if verts == [[0.0, 0.0, 0.0]]: # print(f'verts contains one item of {[[0.0, 0.0, 0.0]]} ... continuing.') continue start_l = verts.pop(0) start_x = start_l[0] # the lib wants to always make Y coods negative. This is likely because of the assumption # the moving physically down on the Y axis puts an object in the correct region. # Basically, negative Y is 'viewable' where as in P5, positive Y is viewable. start_y = -start_l[1] other_verts = [] for o_v in verts: x = o_v[0] # See note above about negative Y values. y = -o_v[1] # z = o_v[2] other_verts.append(x) other_verts.append(y) svg_obj = drawSvg.Lines(start_x, start_y, *other_verts, fill=shape.fill_color, stroke=shape.stroke_color, stroke_width=shape.stroke_width, close=shape.close_path) else: print("Shape found with no verticies... Skipping...") continue svg_canvas.append(svg_obj) svg_canvas.saveSvg(filename) if open_viewer: import webbrowser webbrowser.open('http://example.com') # Go to example.com
g[1] + 1.5 * tile_size, g[0] + math.sqrt(3) * tile_size / 2, g[1] + 0.5 * tile_size) elif num == 2: grad = draw.LinearGradient(g[0], g[1], g[0], g[1] + 2 * tile_size) elif num == 3: grad = draw.LinearGradient(g[0] + math.sqrt(3) * tile_size / 2, g[1] + 1.5 * tile_size, g[0] - math.sqrt(3) * tile_size / 2, g[1] + 0.5 * tile_size) elif num == 4: grad = draw.LinearGradient(g[0] + math.sqrt(3) * tile_size / 2, g[1] + 0.5 * tile_size, g[0] - math.sqrt(3) * tile_size / 2, g[1] + 1.5 * tile_size) else: grad = draw.LinearGradient(g[0], g[1] + 2 * tile_size, g[0], g[1]) img_grad = (1 * g[0] + g[1]) / 20 grad.addStop(1 - math.sqrt(3) / 2, c1) grad.addStop(0.5, c2) grad.addStop(math.sqrt(3) / 2, c3) pic.append( draw.Lines(*flat_points((g[0], g[1]), tile_size), close=True, fill=grad)) pic.setPixelScale(1) # Set number of pixels per geometry unit pic.saveSvg('images\\example.svg')
def draw_catdog(name, param): """ Draws a cat/dog and saves it as an svg Parameters ---------- name : string Filename to save catdog as param : dictionary (keys: variables list) Variable values for the structure of the face """ full_dwg = draw.Drawing(800, 600) dwg = draw.Group() full_dwg.append(dwg) width = 173*(param["face_aspect_ratio"])**0.5 height = 173/(param["face_aspect_ratio"])**0.5 cx = 800/2 cy = 600/2 #Ears ear_angle = param["ear_angle"] ear_tip_angle = param["ear_tip_angle"] ear_length = param["ear_length"] ear_orientation = param["ear_orientation"] ear_point = param["ear_point"] eye_height = param["eye_height"] eye_width = eye_height*param["eye_aspect_ratio"] eye_distance = param["eye_distance"] nose_size = param["nose_size"] fur_color = "hsl(%i, %i%%, %i%%)" % (45,param["fur_saturation"],param["fur_lightness"]) dist_to_tip = r_ellipse(ear_angle,width,height)+ear_length right_tip = dir_point((cx,cy),dist_to_tip,ear_angle) bottom_right = dir_point(right_tip,ear_length*2.2,180+ear_angle+ear_tip_angle*ear_orientation) bottom_right_ctrl = dir_point(bottom_right,ear_length*2.2-ear_point,ear_angle+ear_tip_angle*ear_orientation) top_right = dir_point(right_tip,ear_length*2.2,180+ear_angle-ear_tip_angle*(1-ear_orientation)) top_right_ctrl = dir_point(top_right,ear_length*2.2-ear_point,ear_angle-ear_tip_angle*(1-ear_orientation)) top_left, top_left_ctrl, left_tip, bottom_left_ctrl, bottom_left = mirror([top_right, top_right_ctrl, right_tip, bottom_right_ctrl, bottom_right],cx) left_ear = draw.Path(stroke_width = 1, stroke='black', fill = fur_color) left_ear.M(*bottom_left) left_ear.L(*bottom_left_ctrl) left_ear.A(ear_point*.8, ear_point*.8, 0, False, True, *top_left_ctrl) left_ear.L(*top_left) right_ear = draw.Path(stroke_width = 1, stroke='black', fill = fur_color) right_ear.M(*bottom_right) right_ear.L(*bottom_right_ctrl) right_ear.A(ear_point*.8, ear_point*.8, 0, False, False, *top_right_ctrl) right_ear.L(*top_right) dwg.draw(left_ear) dwg.draw(right_ear) #Face face = ellipse(cx, cy, width, height, stroke_width = 1, stroke='black', fill = fur_color) dwg.draw(face) #Eyes left_eye = ellipse(cx-eye_distance, cy+height/4, eye_width, eye_height, stroke_width = 1, stroke='black', fill = "black") right_eye = ellipse(cx+eye_distance, cy+height/4, eye_width, eye_height, stroke_width = 1, stroke='black', fill = "black") dwg.draw(left_eye) dwg.draw(right_eye) #Nose dwg.draw(draw.Lines(cx-nose_size, cy+nose_size/3, cx+nose_size, cy+nose_size/3, cx,cy-nose_size, close=True, stroke_width = 1, stroke='black', fill = "black")) #Snout dwg.draw(draw.Line(cx,cy-nose_size,cx,cy-nose_size*2.5, stroke_width = 2, stroke='black', fill = "black")) #Mouth mouth = draw.Path(fill = "none", stroke_width = 2, stroke = 'black') mouth.M(cx-nose_size*2,cy-nose_size*2.5-4) mouth.A(nose_size*2, nose_size*2, 30, False, False, cx, cy-nose_size*2.5) mouth.A(nose_size*2, nose_size*2, 150, False, False, cx+nose_size*2, cy-nose_size*2.5-4) dwg.draw(mouth) #Whiskers whisker_length = param["whisker_length"] whiskers = [((cx-34,cy-nose_size-10),195), ((cx-40,cy-nose_size-4),185), ((cx-34,cy-nose_size+2),175), ((cx+34,cy-nose_size-10),345), ((cx+40,cy-nose_size-4),355), ((cx+34,cy-nose_size+2),5) ] for whisker in whiskers: dwg.draw(draw.Line(*whisker[0],*dir_point(whisker[0],whisker_length,whisker[1]), stroke_width = 1, stroke='black', fill = "black")) full_dwg.saveSvg(name)
def draw_constraint(self, url, dn, rc, stroke="red", stroke_width=1): d = self.d arrow = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=stroke_width * 1.5, orient='auto') arrow.append( draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill=stroke, close=True)) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none', stroke_dasharray="4", marker_end=arrow) t = self.tree bdn = t & dn p_bdn = bdn.up brc = t & rc p_brc = brc.up if bdn.x_coor > brc.x_coor: p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none', marker_end=arrow) p.M(bdn.x_coor, bdn.y_coor).L(brc.x_coor, brc.y_coor) d.append(p) else: p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none', marker_end=arrow) p.M(bdn.x_coor, bdn.y_coor).L(brc.x_coor, brc.y_coor) d.append(p) ''' spacing = self.width / (self.n_leaves + 1) total_time = self.total_time width = self.width height = self.height d = self.d stroke_a = stroke_width / 2.0 for n in self.tree.traverse(): # Inner tree, vertical lines x_0, y_0, y_1 = n.x_coor, n.y_coor, self._y_scaling(n.dist) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(x_0, y_0).l(0, y_1) d.append(p) if not n.is_leaf(): c1, c2 = n.get_children() # Horizontal lines # Inner Tree p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(c1.x_coor - stroke_a, n.y_coor).L(c2.x_coor + stroke_a, n.y_coor) d.append(p) d.setRenderSize(h=self.height) d.saveSvg(url) return(d) if node2events[n.name] == "T": p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none', stroke_dasharray="4", marker_end=arrow) if c1.x_coor > c2.x_coor: p.M(c1.x_coor, n.y_coor).L(c2.x_coor + radius, n.y_coor) else: p.M(c1.x_coor, n.y_coor).L(c2.x_coor - radius, n.y_coor) ''' d.saveSvg(url) return (d)
def DrawGeneTree(self, url, genetreefile, eventsgenetreefile, stroke="red", stroke_width=2, displacement=2, r_displacement=0, symbol_scaling=1.5): spacing = self.width / (self.n_leaves + 1) total_time = self.total_time width = self.width height = self.height d = self.d radius = 0.1 genetree_svg = list() r_displacement = random.uniform(-r_displacement, r_displacement) node2events = dict() node2displacement = dict() gtree = self._read_treefile(genetreefile) #################### This a patch to correct a bug in Zombi ################ with open(eventsgenetreefile) as f: f.readline() o, e, nodes = f.readline().strip().split("\t") o_time = float(o) while e != "S" and e != "E" and e != "F" and e != "L" and e != "T" and e != "D": t, e, nodes = f.readline().strip().split("\t") gtree.dist = float(t) - float(o) ############################################################################# # We add the coordinates for n in gtree.traverse(): dist_to_origin = n.get_distance(gtree) + gtree.dist + o_time x_coor, _ = self.node2coor[n.name.split("_")[0]] y_coor = round( self._y_scaling(total_time) - self._y_scaling(dist_to_origin), 2) n.add_feature("displacement", 0) n.add_feature("x_coor", x_coor) n.add_feature("y_coor", y_coor) # We add the displacement gtree.displacement = r_displacement gtree.x_coor += r_displacement for n in gtree.iter_descendants(): parent_d = (n.up).displacement n.displacement = parent_d if n.name in node2displacement: n.displacement += node2displacement[n.name] n.x_coor += (n.displacement * displacement) + r_displacement #n.y_coor += (n.displacement*4) # We get a dict with the new coordinates in the gene tree gnode2x_coor = {n.name: n.x_coor for n in gtree.traverse()} ###### EVENT SYMBOLS ############ arrow = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=stroke_width * 1.5, orient='auto') arrow.append( draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill=stroke, close=True)) ################################## # We add the symbols symbols = list() with open(eventsgenetreefile) as f: f.readline() for l in f: t, e, nodes = l.strip().split("\t") if nodes == "Root": x_coor = gnode2x_coor["Root_1"] else: x_coor = gnode2x_coor["_".join(nodes.split(";")[:2])] if e == "O": o_time = float(t) y_coor = self._y_scaling(total_time) - self._y_scaling( float(t)) symbols.append(("O", x_coor, y_coor)) if e == "D": _, _, node1, num1, node2, num2 = nodes.split(";") n1 = node1 + "_" + num1 n2 = node2 + "_" + num2 node2displacement[n1] = 1 node2displacement[n2] = -1 y_coor = self._y_scaling(total_time) - self._y_scaling( float(t)) symbols.append(("D", x_coor, y_coor)) if e == "L": y_coor = self._y_scaling(total_time) - self._y_scaling( float(t)) symbols.append(("L", x_coor, y_coor)) if e == "T": _, _, node1, num1, node2, num2 = nodes.split(";") n1 = node1 + "_" + num1 n2 = node2 + "_" + num2 node2events["_".join(nodes.split(";")[0:2])] = "T" node2displacement[n2] = 1 #x_coor, _ = self.node2coor[nodes.split(";")[4]] #y_coor = self._y_scaling(float(t)) for n in gtree.traverse(): # Need to check 2 things: is leaf? and has_events? if n.is_leaf() and n.name in node2events: # Vertical line if node2events[n.name] == "L": x_0, y_0, y_1 = n.x_coor, n.y_coor, self._y_scaling(n.dist) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none', marker_end=cross) p.M(x_0, y_0).l(0, y_1) else: x_0, y_0, y_1 = n.x_coor, n.y_coor, self._y_scaling(n.dist) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(x_0, y_0).l(0, y_1) genetree_svg.append(p) elif not n.is_leaf() and n.name in node2events: # Vertical line x_0, y_0, y_1 = n.x_coor, n.y_coor, self._y_scaling(n.dist) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(x_0, y_0).l(0, y_1) genetree_svg.append(p) # Horizontal line c1, c2 = n.get_children() if node2events[n.name] == "T": p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none', stroke_dasharray="4", marker_end=arrow) if c1.x_coor > c2.x_coor: p.M(c1.x_coor, n.y_coor).L(c2.x_coor + radius, n.y_coor) else: p.M(c1.x_coor, n.y_coor).L(c2.x_coor - radius, n.y_coor) genetree_svg.append(p) elif n.is_leaf() and n.name not in node2events: # Vertical line x_0, y_0, y_1 = n.x_coor, n.y_coor, self._y_scaling(n.dist) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(x_0, y_0).l(0, y_1) genetree_svg.append(p) elif not n.is_leaf() and n.name not in node2events: # Vertical line x_0, y_0, y_1 = n.x_coor, n.y_coor, self._y_scaling(n.dist) p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(x_0, y_0).l(0, y_1) genetree_svg.append(p) # Horizontal line c1, c2 = n.get_children() p = draw.Path(stroke=stroke, stroke_width=stroke_width, fill='none') p.M(c1.x_coor - (stroke_width / 2.0), n.y_coor).L(c2.x_coor + (stroke_width / 2.0), n.y_coor) genetree_svg.append(p) # We add the symbols for e, x_coor, y_coor in symbols: if e == "O": p = draw.Circle(cx=x_coor, cy=y_coor, r=stroke_width * symbol_scaling, stroke=stroke, stroke_width=0, fill='green') genetree_svg.append(p) if e == "D": p = draw.Circle(cx=x_coor, cy=y_coor, r=stroke_width * symbol_scaling, stroke=stroke, stroke_width=0, fill='yellow') genetree_svg.append(p) if e == "L": p = draw.Circle(cx=x_coor, cy=y_coor, r=stroke_width * symbol_scaling, stroke=stroke, stroke_width=0, fill='red') genetree_svg.append(p) for element in genetree_svg: d.append(element) d.setRenderSize(h=self.height) d.saveSvg(url) return (d)
def draw_genes(annotations, scale, color_table): '''Draw the genes in annotations. ''' # Define the coordinate system to draw on. # x length is fixed, y depends on how many species/strains are in the # annotations (how many gff's were given to the script) y_len = 25*len(annotations) + 50 # Add 50 for the scale bar d = draw.Drawing(500, y_len, origin = (0,0), displayInline=False) for idx, (k, v) in enumerate(annotations.items()): genes, min, max = v[0], v[1], v[2] contig_length = (max - min)*scale offset = 35 row = y_len-25*(idx+1) # Draw a line for each gff file and it's name d.append(draw.Line(offset, row, contig_length+offset, row, stroke='black', stroke_width=2, fill='none')) d.append(draw.Text(k[:10],6, 2, row-1, text_anchor="left", fill='black')) for gene in genes: color = color_table[gene[4]] arrow = draw.Marker(0, -0.5, 1, 0.5, scale=2, orient='auto') arrow.append(draw.Lines(0, -0.5, 0, 0.5, 1, 0, fill=color, close=True)) # Draw genes gene_coords = ((gene[1] - min)*scale+offset, \ (gene[2] - min)*scale+offset) midpoint = int(gene_coords[0] + (gene_coords[1] - gene_coords[0]) / 2) arrow_positions = [] # Shit code but extend the arrows from the midpoint in both # directions i = midpoint while i < int(gene_coords[1])-1: arrow_positions.append(i) i += int(600*scale) i = midpoint - int(600*scale) while i > int(gene_coords[0])+1: arrow_positions.append(i) i -= int(600*scale) # To draw arrows in correct orientation k = -1 if gene[3] == "+" else 1 # Draw a line for each gene d.append(draw.Line(gene_coords[0], row, gene_coords[1], row, \ stroke=color, stroke_width=6, fill='none')) # Draw arrows on the gene, depending on orientation for a in arrow_positions: d.append(draw.Lines(a+k, row+2, a-k, row, a+k, row-2, \ stroke_width=0.2, stroke = "black",fill="none")) ## Draw labels # Gene name, skip hypothetical proteins if gene[4] != "hypothetical_protein": text_pos = gene_coords[0] + (gene_coords[1]-gene_coords[0])/2 d.append(draw.Text(gene[4],5, text_pos,row-10, center=0.6, text_anchor="middle", fill='black')) # Draw a scale bar longest = int(450 / scale) row = 25 d.append(draw.Line(offset, row, longest, row, stroke='black', stroke_width=1, fill='none')) for num in range(0,longest, 1000): scaled_num = num*scale + offset d.append(draw.Line(scaled_num, row+1.5, scaled_num, row-1.5, stroke='black', stroke_width=0.5, fill='none')) d.append(draw.Text(str(num), 3, scaled_num, row-5, center=True, fill='black')) d.setPixelScale(2) d.saveSvg('test.svg')
def save_svg(output_path, window_w, window_h): dq = p5.renderer.draw_queue # why is this different from the the specified canvas size? is there a transform/scaler soemwhere? # print(p5.sketch.size) svg_canvas = drawSvg.Drawing(window_w, window_h, origin=(0, 0), displayInline=False) # There may be a better way to do this through the init above, but I found it confusing. # it was much easier to just hardcode it. svg_canvas.viewBox = (0, 0, window_w, window_h) for geo, meta in dq: if geo == 'lines': vertices, edges, stroke, stroke_weight, stroke_cap, stroke_join = meta verts = vertices.tolist() if verts == [[0.0, 0.0, 0.0]]: # print(f'verts contains one item of {[[0.0, 0.0, 0.0]]} ... continuing.') continue start_l = verts.pop(0) start_x = start_l[0] # the lib wants to always make Y coods negative. This is likely because of the assumption # the moving physically down on the Y axis puts an object in the correct region. # Basically, negative Y is 'viewable' where as in P5, positive Y is viewable. start_y = -start_l[1] other_verts = [] for o_v in verts: x = o_v[0] # See note above about negative Y values. y = -o_v[1] z = o_v[2] other_verts.append(x) other_verts.append(y) svg_obj = drawSvg.Lines(start_x, start_y, *other_verts, fill='none', stroke='black', stroke_width=stroke_weight, close=False) elif geo == 'triangles': vertices, idx, fill = meta verts = vertices.tolist() start_l = verts.pop(0) start_x = start_l[0] # See note above about negative Y values. start_y = -start_l[1] other_verts = [] for o_v in verts: x = o_v[0] # See note above about negative Y values. y = -o_v[1] z = o_v[2] other_verts.extend([x, y]) svg_obj = drawSvg.Lines(start_x, start_y, *other_verts, fill='none', stroke='black', stroke_width=2, close=False) else: # A good example is 'Points" # elif geo == 'points': # vertices, idx, stroke = meta # raise NotImplemented() raise NotImplemented("This geometry is not implemented yet.") svg_canvas.append(svg_obj) svg_canvas.saveSvg(output_path) del svg_canvas
import drawSvg as draw d = draw.Drawing(200, 100, origin='center', displayInline=False) # Draw an irregular polygon d.append( draw.Lines(-80, -45, 70, -49, 95, 49, -90, 40, close=False, fill='#eeee00', stroke='black')) # Draw a rectangle d.append(draw.Rectangle(0, 0, 40, 50, fill='#1248ff')) # Draw a circle d.append(draw.Circle(-40, -10, 30, fill='red', stroke_width=2, stroke='black')) # Draw an arbitrary path (a triangle in this case) p = draw.Path(stroke_width=2, stroke='green', fill='black', fill_opacity=0.5) p.M(-30, 5) # Start path at point (-30, 5) p.l(60, 30) # Draw line to (60, 30) p.h(-70) # Draw horizontal line to x=-70 p.Z() # Draw line to start d.append(p)
def draw_line(x1, y1, x2, y2): return draw.Lines(x1, y1, x2, y2, close=False, fill='none', stroke='red')
def arrow(scale, color): marker = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=scale, orient='auto') marker.append( draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill=color, close=True)) return marker
# Ref: http://www.petercollingridge.co.uk/tools/drawsvgpy/ # import drawSvg as draw d = draw.Drawing(200, 100, origin='center') d.append( draw.Lines(-80, -45, 70, -49, 95, 49, -90, 40, close=False, fill='#eeee00', stroke='black')) d.append(draw.Rectangle(0, 0, 40, 50, fill='#1248ff')) d.append(draw.Circle(-40, -10, 30, fill='red', stroke_width=2, stroke='black')) p = draw.Path(stroke_width=2, stroke='green', fill='black', fill_opacity=0.5) p.M(-30, 5) # Start path at point (-30, 5) p.l(60, 30) # Draw line to (60, 30) p.h(-70) # Draw horizontal line to x=-70 p.Z() # Draw line to start d.append(p) d.append( draw.ArcLine(60,
def draw_img(change): dwg.children.clear() width = 173 * (levers["face_aspect_ratio"].value)**0.5 height = 173 / (levers["face_aspect_ratio"].value)**0.5 cx = 800 / 2 cy = 600 / 2 #Ears ear_angle = levers["ear_angle"].value ear_tip_angle = levers["ear_tip_angle"].value ear_length = levers["ear_length"].value ear_orientation = levers["ear_orientation"].value ear_point = levers["ear_point"].value eye_height = levers["eye_height"].value eye_width = eye_height * levers["eye_aspect_ratio"].value eye_distance = levers["eye_distance"].value nose_size = levers["nose_size"].value fur_color = "hsl(%i, %i%%, %i%%)" % ( 45, levers["fur_saturation"].value, levers["fur_lightness"].value) dist_to_tip = r_ellipse(ear_angle, width, height) + ear_length right_tip = dir_point((cx, cy), dist_to_tip, ear_angle) bottom_right = dir_point( right_tip, ear_length * 2.2, 180 + ear_angle + ear_tip_angle * ear_orientation) bottom_right_ctrl = dir_point( bottom_right, ear_length * 2.2 - ear_point, ear_angle + ear_tip_angle * ear_orientation) top_right = dir_point( right_tip, ear_length * 2.2, 180 + ear_angle - ear_tip_angle * (1 - ear_orientation)) top_right_ctrl = dir_point( top_right, ear_length * 2.2 - ear_point, ear_angle - ear_tip_angle * (1 - ear_orientation)) top_left, top_left_ctrl, left_tip, bottom_left_ctrl, bottom_left = mirror( [ top_right, top_right_ctrl, right_tip, bottom_right_ctrl, bottom_right ], cx) left_ear = draw.Path(stroke_width=1, stroke='black', fill=fur_color) left_ear.M(*bottom_left) left_ear.L(*bottom_left_ctrl) left_ear.A(ear_point * .8, ear_point * .8, 0, False, True, *top_left_ctrl) left_ear.L(*top_left) right_ear = draw.Path(stroke_width=1, stroke='black', fill=fur_color) right_ear.M(*bottom_right) right_ear.L(*bottom_right_ctrl) right_ear.A(ear_point * .8, ear_point * .8, 0, False, False, *top_right_ctrl) right_ear.L(*top_right) dwg.draw(left_ear) dwg.draw(right_ear) #Face face = ellipse(cx, cy, width, height, stroke_width=1, stroke='black', fill=fur_color) dwg.draw(face) #Eyes left_eye = ellipse(cx - eye_distance, cy + height / 4, eye_width, eye_height, stroke_width=1, stroke='black', fill="black") right_eye = ellipse(cx + eye_distance, cy + height / 4, eye_width, eye_height, stroke_width=1, stroke='black', fill="black") dwg.draw(left_eye) dwg.draw(right_eye) #Nose dwg.draw( draw.Lines(cx - nose_size, cy + nose_size / 3, cx + nose_size, cy + nose_size / 3, cx, cy - nose_size, close=True, stroke_width=1, stroke='black', fill="black")) #Snout dwg.draw( draw.Line(cx, cy - nose_size, cx, cy - nose_size * 2.5, stroke_width=2, stroke='black', fill="black")) #Mouth mouth = draw.Path(fill="none", stroke_width=2, stroke='black') mouth.M(cx - nose_size * 2, cy - nose_size * 2.5 - 4) mouth.A(nose_size * 2, nose_size * 2, 30, False, False, cx, cy - nose_size * 2.5) mouth.A(nose_size * 2, nose_size * 2, 150, False, False, cx + nose_size * 2, cy - nose_size * 2.5 - 4) dwg.draw(mouth) #Whiskers whisker_length = levers["whisker_length"].value whiskers = [((cx - 34, cy - nose_size - 10), 195), ((cx - 40, cy - nose_size - 4), 185), ((cx - 34, cy - nose_size + 2), 175), ((cx + 34, cy - nose_size - 10), 345), ((cx + 40, cy - nose_size - 4), 355), ((cx + 34, cy - nose_size + 2), 5)] for whisker in whiskers: dwg.draw( draw.Line(*whisker[0], *dir_point(whisker[0], whisker_length, whisker[1]), stroke_width=1, stroke='black', fill="black")) animal.refresh()
def draw_pin_shape(self, d, x, y, ox, oy, pin_orientation, shape_of_pin, pen=5): # inverted pin draw a circle of radius 10 at the end of the pin. if shape_of_pin == "I": # inverted (not gate) d = self.drawCircle(d, x, y, self.RADIUS_OF_NOT_GATE, fill=self.FILL_NOT_GATE) elif shape_of_pin == "C": # clock # ox oy are original values x = ox y = oy clock_pin_padding = 30 if pin_orientation == "R": # add in x direction x1 = x + clock_pin_padding y1 = y x2 = x y2 = y + clock_pin_padding x3 = x y3 = y - clock_pin_padding arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines(*arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, fill_opacity=0)) elif pin_orientation == "L": # sub in x direction x1 = x - clock_pin_padding y1 = y x2 = x y2 = y + clock_pin_padding x3 = x y3 = y - clock_pin_padding arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines( *arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, )) elif pin_orientation == "U": # add in y direction x1 = x y1 = y + clock_pin_padding x2 = x + clock_pin_padding y2 = y x3 = x - clock_pin_padding y3 = y arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines( *arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, )) elif pin_orientation == "D": # sub in y direction x1 = x y1 = y - clock_pin_padding x2 = x + clock_pin_padding y2 = y x3 = x - clock_pin_padding y3 = y arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines( *arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, )) else: pass elif shape_of_pin == "CI": # clock inverted x = ox y = oy clock_pin_padding = 30 inverted_clock_radius = 10 if pin_orientation == "R": # add in x direction x1 = x + clock_pin_padding y1 = y x2 = x y2 = y + clock_pin_padding x3 = x y3 = y - clock_pin_padding arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines(*arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, fill_opacity=0)) d.append( draw.Circle(x + inverted_clock_radius, y, inverted_clock_radius, stroke_width=pen, stroke=self.STROKE_COLOR, fill_opacity=0)) elif pin_orientation == "L": # sub in x direction x1 = x - clock_pin_padding y1 = y x2 = x y2 = y + clock_pin_padding x3 = x y3 = y - clock_pin_padding arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines( *arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, )) d.append( draw.Circle(x - inverted_clock_radius, y, inverted_clock_radius, stroke_width=pen, stroke=self.STROKE_COLOR, fill_opacity=0)) elif pin_orientation == "U": # add in y direction x1 = x y1 = y + clock_pin_padding x2 = x + clock_pin_padding y2 = y x3 = x - clock_pin_padding y3 = y arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines( *arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, )) d.append( draw.Circle(x, y + inverted_clock_radius, inverted_clock_radius, stroke_width=pen, stroke=self.STROKE_COLOR, fill_opacity=0)) elif pin_orientation == "D": # sub in y direction x1 = x y1 = y - clock_pin_padding x2 = x + clock_pin_padding y2 = y x3 = x - clock_pin_padding y3 = y arg = [x1, y1, x2, y2, x3, y3, x1, y1] d.append( draw.Lines( *arg, close=False, stroke_width=pen, stroke=self.STROKE_COLOR, )) d.append( draw.Circle(x, y - inverted_clock_radius, inverted_clock_radius, stroke_width=pen, stroke=self.STROKE_COLOR, fill_opacity=0)) else: pass elif shape_of_pin == "L": # input low pass elif shape_of_pin == "CL": # clock low pass elif shape_of_pin == "V": # output low pass elif shape_of_pin == "F": # falling edge clock pass elif shape_of_pin == "X": # non logic pass return d
def draw_svg_design(file_name: str = 'example.png', text="Template", add_svg_elements: bool = False, rects_max_n: int = 2, poly_max_n: int = 2, circles_max_n: int = 2, poly_max_n_points: int = 4, color_style: str = 'epic', font_color: tuple = (0, 0, 0), sharpen: float = 0.5, font_family: str or None = None, image_size: tuple = (512, 512), margin: tuple = (50, 50), seed=None) -> tuple: if seed is not None: random.seed(seed) if font_family is None: font_family = random.choice(FONTS) p = Palette(color_style) d = draw.Drawing(image_size[0], image_size[1], displayInline=False) import_statement = Style( '@import url(http://fonts.googleapis.com/css?family=Dela+Gothic+One);') d.append(import_statement) poly_max_n += int(poly_max_n * sharpen) # Drawing polygons for _ in range(0, poly_max_n): # Generating points for polygon points_ = [(random.randint(0 + margin[0], image_size[0] - margin[0]), random.randint(0 + margin[1], image_size[1] - margin[1])) for _ in range(random.randint(3, poly_max_n_points))] # Concatenating all coordinates points = list(itertools.chain(*points_)) poly_params = {} if random.random() < 0.6: poly_params["fill"] = p.next_rgb() else: poly_params["stroke"] = p.next_rgb() poly_params["stroke_width"] = random.randint(3, 10) poly_params["fill"] = "rgba(0, 0, 0, 0.001)" if sharpen < 0: poly_params['stroke-linejoin'] = "round" elif sharpen > 0: poly_params['stroke-linejoin'] = "miter" else: poly_params['stroke-linejoin'] = "round" if random.random( ) > 0.5 else "miter" # Drawing d.append(draw.Lines(*points, close=True, **poly_params)) circles_max_n += int(circles_max_n * -sharpen) for _ in range(circles_max_n): circle_params = {} if random.random() < 0.6: circle_params["fill"] = p.next_rgb() else: circle_params["stroke"] = p.next_rgb() circle_params["stroke_width"] = random.randint(3, 6) circle_params["fill"] = "rgba(0, 0, 0, 0.001)" s = random.randint(30, 100) cx, cy = random.randint(margin[0], image_size[0] - s // 2), random.randint( margin[1], image_size[1] - s // 2) circle = draw.Circle(cx, cy, s, **circle_params) d.append(circle) # Draw text r, g, b = font_color font_size = random.randint(70, 150) x, y = random.randint(margin[0], image_size[0] // 2), random.randint( margin[1] * 2, image_size[1] - margin[1] * 2) d.append( draw.Text(text, font_size, x, y, fill=f'rgb({r}, {g}, {b})', font_family=font_family)) """icon_d = draw.Drawing(image_size[0], image_size[1], displayInline=False) svg_icon = vectorize(public_id="sgtb7y5h12lg3xwdgyct") path = parse_svg_paths(svg_icon)[-1][9:-3] icon_d.append(draw.Path(path, stroke="black", fill=p.next_rgb(), transform='rotate(180 0 0)')) d.append(SVG(icon_d.asSvg()))""" ret_params = { 'font_color': font_color, 'font_family': font_family, 'seed': seed, 'circles_n': circles_max_n, 'poly_n': poly_max_n, 'sharpen': sharpen, 'font_size': font_size } """ # Draw a rectangle # Draw a circle d.append(draw.Circle(-40, -10, 30, fill='red', stroke_width=2, stroke='black')) # Draw an arbitrary path (a triangle in this case) p = draw.Path(stroke_width=2, stroke='lime', fill='black', fill_opacity=0.2) p.M(-10, 20) # Start path at point (-10, 20) p.C(30, -10, 30, 50, 70, 20) # Draw a curve to (70, 20) d.append(p) d.append(draw.Text('Path text', 8, path=p, text_anchor='start', valign='middle')) d.append(draw.Text(['Multi-line', 'text'], 8, path=p, text_anchor='end')) # Draw multiple circular arcs d.append(draw.ArcLine(60, -20, 20, 60, 270, stroke='red', stroke_width=5, fill='red', fill_opacity=0.2)) d.append(draw.Arc(60, -20, 20, 60, 270, cw=False, stroke='green', stroke_width=3, fill='none')) d.append(draw.Arc(60, -20, 20, 270, 60, cw=True, stroke='blue', stroke_width=1, fill='black', fill_opacity=0.3)) # Draw arrows arrow = draw.Marker(-0.1, -0.5, 0.9, 0.5, scale=4, orient='auto') arrow.append(draw.Lines(-0.1, -0.5, -0.1, 0.5, 0.9, 0, fill='red', close=True)) p = draw.Path(stroke='red', stroke_width=2, fill='none', marker_end=arrow) # Add an arrow to the end of a path p.M(20, -40).L(20, -27).L(0, -20) # Chain multiple path operations d.append(p) d.append(draw.Line(30, -20, 0, -10, stroke='red', stroke_width=2, fill='none', marker_end=arrow)) # Add an arrow to the end of a line d.setPixelScale(2) # Set number of pixels per geometry unit # d.setRenderSize(400,200) # Alternative to setPixelScale""" return ret_params, d.asSvg()