def testMultiplePaths(): """ - | - """ t=Turtle() t.penDown() print(t.getPosition()) t.forward(205) print(t.getPosition()) t.right(90) t.forward(205) print(t.getPosition()) t.right(90) t.forward(105) print(t.getPosition()) t.right(90) t.forward(105) print(t.getPosition()) t.penUp() t.moveTo(Vector(300,300)) print(t.getPosition()) t.penDown() t.forward(205) print(t.getPosition()) t.finish() #print (t.getXML()) s=Svg(0, 0, 2000, 2000) s=t.addTurtlePathToSVG(s) s.save('./testoutput/testTurtle.svg')
def get_pairing_contents(self, index: int, width: int, height: int) -> str: """ Generate an SVG comparing the query to a single hit cluster Arguments: index: the index of the hit width: the width of the SVG height: the height of the SVG Returns: a string containing the SVG XML """ svg = Svg(x=0, y=0, width=width, height=height) viewbox = "0 0 %d %d" % (width, height) svg.set_viewBox(viewbox) svg.set_preserveAspectRatio("none") max_length = max([len(self.query_cluster), len(self.hits[index])]) scaling = (width - 20) / max_length for i, cluster in enumerate([self.query_cluster, self.hits[index]]): for group in cluster.get_svg_groups( v_offset=50 * i, h_offset=(max_length - len(cluster)) // 2, scaling=scaling, colours=self.colour_lookup): svg.addElement(group) return svg.getXML()
def __init__(self, timeline, scene, view_properties, appearence, **kwargs): self._timeline = timeline self._scene = scene self._appearence = appearence self._view_properties = view_properties self._svg = Svg(width=scene.width, height=scene.height) self._small_font_style = self._get_small_font_style() self._small_centered_font_style = self._get_small_centered_font_style() self._larger_font_style = self._get_larger_font_style() try: self._shadow_flag = kwargs["shadow"] except KeyError: self._shadow_flag = False
def testLindenMayer(): s=Svg(0, 0, 2000, 2000) commands='F+F-F-FF+F+F-F+F+F-F-FF+F+F-F+F+F-F-FF+F+F-F+F+F-F-FF+F+F-F' t=Turtle() t.moveTo(Vector(500,250)) t.penDown() angle=90 distance=40 for cmd in commands: print(cmd) if cmd=='F': t.forward(distance) elif cmd=='+': t.right(angle) elif cmd=='-': t.left(angle) print(t.getPosition()) t.penDown() print (t.getXML()) s=t.addTurtlePathToSVG(s) s.save('./testoutput/testTurtle.svg')
def testLindenMayer(): s = Svg(0, 0, 2000, 2000) commands = 'F+F-F-FF+F+F-F+F+F-F-FF+F+F-F+F+F-F-FF+F+F-F+F+F-F-FF+F+F-F' t = Turtle() t.moveTo(Vector(500, 250)) t.penDown() angle = 90 distance = 40 for cmd in commands: print(cmd) if cmd == 'F': t.forward(distance) elif cmd == '+': t.right(angle) elif cmd == '-': t.left(angle) print(t.getPosition()) t.penDown() print(t.getXML()) s = t.addTurtlePathToSVG(s) s.save('./testoutput/testTurtle.svg')
def draw_tree(width, height): global nameStyle nameStyle = StyleBuilder() nameStyle.setFontFamily(fontfamily=dc.nameFont) nameStyle.setFontSize("%spt" % dc.nameFontSize) nameStyle.setTextAnchor("left") nameStyle = nameStyle.getStyle() svg = Svg(width=width, height=height) # draw nodes for depth in depthToNames: for name in depthToNames[depth]: node = nameToNode[name] draw_node(svg, node, name) # draw branches for depth in depthToNames: for name in depthToNames[depth]: node = nameToNode[name] isLeaf = (node.children == []) if (isLeaf): continue numChildren = len(node.children) for (i, child) in enumerate(node.children): rootFrac = 0.4 + (0.2 * i) / (numChildren - 1) sinkFrac = 0.5 if (orientation == "T2B"): (rootX, rootY) = (node.x + rootFrac * dc.nodeWdt, node.y + dc.nodeHgt) (sinkX, sinkY) = (child.x + sinkFrac * dc.nodeWdt, child.y) draw_vert_branch(svg, "%s_branch_%d" % (name, i), rootX, rootY, sinkX, sinkY) else: # if (orientation == "L2R"): (rootX, rootY) = (node.x + dc.nodeWdt, node.y + rootFrac * dc.nodeHgt) (sinkX, sinkY) = (child.x, child.y + sinkFrac * dc.nodeHgt) draw_horz_branch(svg, "%s_branch_%d" % (name, i), rootX, rootY, sinkX, sinkY) return svg
def build_svg(self, outfile): # background image bg_image = PIL.Image.open(self.image_filename) width, height = bg_image.size # build a svg object, which size is the same as background svg = Svg(width=width, height=height) # add background image png_link = bg_image.filename if self.mode == 'online': path = os.path.basename(self.image_filename).split('.')[0] png_link = '{}/get/{}/image'.format(KEGGRest.KEGG_API_URL, path) elif self.mode == 'base64': with safe_open(self.image_filename, 'rb') as f: png_link = 'data:image/png;base64,' + base64.b64encode( f.read()) im = Image(x=0, y=0, width=width, height=height) im.set_xlink_href(png_link) svg.addElement(im) for shape, position, url, title in self.conf_data: a = A(target='new_window') a.set_xlink_title(title) a.set_xlink_href(KEGGRest.KEGG_BASE_URL + url) # check gene, and add a highlighted rect or circle color = get_gene_color(title, self.genedict) child = self.add_child(shape, position, color) if child: a.addElement(child) svg.addElement(a) svg.save(outfile, encoding='UTF-8', standalone='no')
def get_overview_contents(self, width: int, height: int) -> str: """ Generate an SVG comparing the query to all hit cluster Arguments: width: the width of the SVG height: the height of the SVG Returns: a string containing the SVG XML """ svg = Svg(x=0, y=0, width=width, height=height) viewbox = "0 0 %d %d" % (width, height) svg.set_viewBox(viewbox) svg.set_preserveAspectRatio("none") scaling = (width - 20) / self.max_length # -20 for margins offset = (self.max_length - len(self.query_cluster)) // 2 for group in self.query_cluster.get_svg_groups( h_offset=offset, scaling=scaling, colours=self.colour_lookup, overview=True, prefix=self.prefix): svg.addElement(group) for index, cluster in enumerate(self.hits): for group in cluster.get_svg_groups( v_offset=50 * (index + 1), h_offset=(self.max_length - len(cluster)) // 2, scaling=scaling, colours=self.colour_lookup, overview=True, prefix=self.prefix): svg.addElement(group) return svg.getXML()
def draw_tree(): if (dc.nameFontSize < 16): scale = dc.nameFontSize / 16.0 nameCapsHgt = dc.nameCapsHgt * scale nameDescHgt = dc.nameDescHgt * scale nameFontLineHgt = dc.nameFontLineHgt * scale nameStyle = StyleBuilder() nameStyle.setFontFamily(fontfamily=dc.nameFont) nameStyle.setFontSize("%spt" % dc.nameFontSize) nameStyle.setTextAnchor("left") nameStyle = nameStyle.getStyle() svg = Svg() # draw nodes for depth in depthToNames: for name in depthToNames[depth]: node = nameToNode[name] isLeaf = (node.left == None) yLine = node.y + nameCapsHgt + 1 ob = SvgRect(node.x, node.y, dc.nodeWidth, dc.nodeHeight, id="%s_box" % name) ob.set_stroke(dc.lineColor) ob.set_stroke_width(dc.lineThickness) if (isLeaf): ob.set_fill(dc.leafFillColor) else: ob.set_fill(dc.nodeFillColor) svg.addElement(ob) ob = SvgText("%s" % name, node.x + 1, yLine, id="%s_name" % node) ob.set_style(nameStyle) svg.addElement(ob) yLine += nameFontLineHgt if (hasattr(node, "bitsUnion")): ob = SvgText("U:" + bits_to_string(node.numBits, node.bitsUnion), node.x + 1, yLine, id="%s_Bunion" % node) ob.set_style(nameStyle) svg.addElement(ob) yLine += nameFontLineHgt if (hasattr(node, "bitsIntersection")): ob = SvgText( "I:" + bits_to_string(node.numBits, node.bitsIntersection), node.x + 1, yLine, id="%s_Bintersection" % node) ob.set_style(nameStyle) svg.addElement(ob) yLine += nameFontLineHgt if (hasattr(node, "bitsAll")): ob = SvgText("A:" + bits_to_string(node.numBits, node.bitsAll), node.x + 1, yLine, id="%s_Ball" % node) ob.set_style(nameStyle) svg.addElement(ob) yLine += nameFontLineHgt if (hasattr(node, "bitsSome")): ob = SvgText("S:" + bits_to_string(node.numBits, node.bitsSome), node.x + 1, yLine, id="%s_Bsome" % node) ob.set_style(nameStyle) svg.addElement(ob) yLine += nameFontLineHgt # draw branches for depth in depthToNames: for name in depthToNames[depth]: node = nameToNode[name] if (node.left == None): continue (leftStartX, leftStartY) = (node.x + 0.4 * dc.nodeWidth, node.y + dc.nodeHeight) (rightStartX, rightStartY) = (node.x + 0.6 * dc.nodeWidth, node.y + dc.nodeHeight) (leftEndX, leftEndY) = (node.left.x + 0.5 * dc.nodeWidth, node.right.y) (rightEndX, rightEndY) = (node.right.x + 0.5 * dc.nodeWidth, node.right.y) draw_branch(svg, "%s_left_branch" % node, leftStartX, leftStartY, leftEndX, leftEndY) draw_branch(svg, "%s_right_branch" % node, rightStartX, rightStartY, rightEndX, rightEndY) return svg
class SVGDrawingAlgorithm(object): # options: shadow=True|False def __init__(self, timeline, scene, view_properties, appearence, **kwargs): self._timeline = timeline self._scene = scene self._appearence = appearence self._view_properties = view_properties self._svg = Svg(width=scene.width, height=scene.height) self._small_font_style = self._get_small_font_style() self._small_centered_font_style = self._get_small_centered_font_style() self._larger_font_style = self._get_larger_font_style() try: self._shadow_flag = kwargs["shadow"] except KeyError: self._shadow_flag = False def write(self, path): """ write the SVG code into the file with filename path. No checking is done if file/path exists """ self._svg.save(path, encoding=ENCODING) def draw(self): for element in self._get_elements(): self._svg.addElement(element) def _get_elements(self): elements = [self._define_shadow_filter(), self._get_bg()] elements.extend(self._get_events()) elements.extend(self._get_legend()) return elements def _get_events(self): return [ self._draw_event(event, rect) for (event, rect) in self._scene.event_data ] def _get_legend(self): categories = self._extract_categories() return [ item for item in [self._draw_legend(categories)] if self._legend_should_be_drawn(categories) ] def _get_bg(self): """ Draw background color Draw background Era strips and labels Draw major and minor strips, lines to all event boxes and baseline. Both major and minor strips have divider lines and labels. Draw now line if it is visible """ group = G() group.addElement(self._draw_background()) for era in self._timeline.get_all_periods(): group.addElement(self._draw_era_strip(era)) group.addElement(self._draw_era_text(era)) for strip in self._scene.minor_strip_data: group.addElement( self._draw_minor_strip_divider_line(strip.end_time)) group.addElement(self._draw_minor_strip_label(strip)) for strip in self._scene.major_strip_data: group.addElement( self._draw_major_strip_divider_line(strip.end_time)) group.addElement(self._draw_major_strip_label(strip)) group.addElement(self._draw_divider_line()) self._draw_lines_to_non_period_events(group, self._view_properties) if self._now_line_is_visible(): group.addElement(self._draw_now_line()) return group def _draw_background(self): svg_color = self._map_svg_color(self._appearence.get_bg_colour()) return ShapeBuilder().createRect(0, 0, self._scene.width, self._scene.height, fill=svg_color) def _draw_era_strip(self, era): svg_color = self._map_svg_color(era.get_color()) x, width = self._calc_era_strip_metrics(era) return ShapeBuilder().createRect(x, INNER_PADDING, width, self._scene.height - 2 * INNER_PADDING, fill=svg_color, strokewidth=0) def _draw_era_text(self, era): x, y = self._calc_era_text_metrics(era) return self._draw_label(era.get_name(), x, y, self._small_centered_font_style) def _calc_era_strip_metrics(self, era): period = era.get_time_period() x = self._scene.x_pos_for_time(period.start_time) width = min(self._scene.x_pos_for_time(period.end_time), self._scene.width) - x return x, width def _calc_era_text_metrics(self, era): period = era.get_time_period() _, width = self._calc_era_strip_metrics(era) x = self._scene.x_pos_for_time(period.start_time) + width // 2 y = self._scene.height - OUTER_PADDING return x, y def _draw_minor_strip_divider_line(self, time): return self._draw_vertical_line(self._scene.x_pos_for_time(time), "lightgrey") def _draw_minor_strip_label(self, strip_period): label = self._scene.minor_strip.label(strip_period.start_time) x = self._calc_x_for_minor_strip_label(strip_period) y = self._calc_y_for_minor_strip_label() return self._draw_label(label, x, y, self._small_font_style) def _calc_x_for_minor_strip_label(self, strip_period): return (self._scene.x_pos_for_time(strip_period.start_time) + self._scene.x_pos_for_time( strip_period.end_time)) // 2 - SMALL_FONT_SIZE_PX def _calc_y_for_minor_strip_label(self): return self._scene.divider_y - OUTER_PADDING def _draw_label(self, label, x, y, style): text = self._text(label, x, y) text.set_style(style.getStyle()) return text def _draw_major_strip_divider_line(self, time): return self._draw_vertical_line(self._scene.x_pos_for_time(time), "black") def _draw_vertical_line(self, x, colour): return ShapeBuilder().createLine(x, 0, x, self._scene.height, strokewidth=0.5, stroke=colour) def _draw_major_strip_label(self, tp): label = self._scene.major_strip.label(tp.start_time, True) # If the label is not visible when it is positioned in the middle # of the period, we move it so that as much of it as possible is # visible without crossing strip borders. # since there is no function like textwidth() for SVG, just take into account that text can be overwritten # do not perform a special handling for right border, SVG is unlimited x = (max(0, self._scene.x_pos_for_time(tp.start_time)) + min( self._scene.width, self._scene.x_pos_for_time(tp.end_time))) // 2 y = LARGER_FONT_SIZE_PX + OUTER_PADDING return self._draw_label(label, x, y, self._larger_font_style) def _draw_divider_line(self): return ShapeBuilder().createLine(0, self._scene.divider_y, self._scene.width, self._scene.divider_y, strokewidth=0.5, stroke="grey") def _draw_lines_to_non_period_events(self, group, view_properties): for (event, rect) in self._scene.event_data: if rect.Y < self._scene.divider_y: line, circle = self._draw_line_to_non_period_event( view_properties, event, rect) group.addElement(line) group.addElement(circle) def _draw_line_to_non_period_event(self, view_properties, event, rect): x = self._scene.x_pos_for_time(event.mean_time()) y = rect.Y + rect.Height // 2 stroke = { True: "red", False: "black" }[view_properties.is_selected(event)] line = ShapeBuilder().createLine(x, y, x, self._scene.divider_y, stroke=stroke) circle = ShapeBuilder().createCircle(x, self._scene.divider_y, 2) return line, circle def _draw_now_line(self): return self._draw_vertical_line(self._scene.x_pos_for_now(), "darkred") def _now_line_is_visible(self): x = self._scene.x_pos_for_now() return x > 0 and x < self._scene.width def _get_event_border_color(self, event): return self._map_svg_color(darken_color(self._get_event_color(event))) def _get_event_box_color(self, event): return self._map_svg_color(self._get_event_color(event)) def _get_box_indicator_color(self, event): return self._map_svg_color( darken_color(self._get_event_color(event), 0.6)) def _get_event_color(self, event): if event.category: return event.category.color else: return event.get_default_color() def _map_svg_color(self, color): """ map (r,g,b) color to svg string """ return "#%02X%02X%02X" % color[:3] def _legend_should_be_drawn(self, categories): return self._appearence.get_legend_visible() and len(categories) > 0 def _extract_categories(self): return sort_categories( unique_based_on_eq(event.category for (event, _) in self._scene.event_data if event.category)) def _draw_legend(self, categories): """ Draw legend for the given categories. Box in lower right corner Motivation for positioning in right corner: SVG text cannot be centered since the text width cannot be calculated and the first part of each event text is important. ergo: text needs to be left aligned. But then the probability is high that a lot of text is at the left bottom ergo: put the legend to the right. +----------+ | Name O | | Name O | +----------+ """ group = G() group.addElement(self._draw_categories_box(len(categories))) cur_y = self._get_categories_box_y(len(categories)) + OUTER_PADDING for cat in categories: color_box, label = self._draw_category( self._get_categories_box_width(), self._get_categories_item_height(), self._get_categories_box_x(), cur_y, cat) group.addElement(color_box) group.addElement(label) cur_y = cur_y + self._get_categories_item_height() + INNER_PADDING return group def _draw_categories_box(self, nbr_of_categories): return ShapeBuilder().createRect( self._get_categories_box_x(), self._get_categories_box_y(nbr_of_categories), self._get_categories_box_width(), self._get_categories_box_height(nbr_of_categories), fill='white') def _get_categories_box_width(self): # reserve 15% for the legend return int(self._scene.width * 0.15) def _get_categories_item_height(self): return SMALL_FONT_SIZE_PX + OUTER_PADDING def _get_categories_box_height(self, nbr_of_categories): return nbr_of_categories * (self._get_categories_item_height( ) + INNER_PADDING) + 2 * OUTER_PADDING - INNER_PADDING def _get_categories_box_x(self): return self._scene.width - self._get_categories_box_width( ) - OUTER_PADDING def _get_categories_box_y(self, nbr_of_categories): return self._scene.height - self._get_categories_box_height( nbr_of_categories) - OUTER_PADDING def _draw_category(self, width, item_height, x, y, cat): return (self._draw_category_color_box(item_height, x, y, cat), self._draw_category_label(width, item_height, x, y, cat)) def _draw_category_color_box(self, item_height, x, y, cat): base_color = self._map_svg_color(cat.color) border_color = self._map_svg_color(darken_color(cat.color)) return ShapeBuilder().createRect(x + OUTER_PADDING, y, item_height, item_height, fill=base_color, stroke=border_color) def _draw_category_label(self, width, item_height, x, y, cat): return self._svg_clipped_text( cat.name, (x + OUTER_PADDING + INNER_PADDING + item_height, y, width - OUTER_PADDING - INNER_PADDING - item_height, item_height), self._get_small_font_style()) def _draw_event(self, event, rect): if self._scene.center_text(): style = self._small_centered_font_style else: style = self._small_font_style group = G() group.addElement(self._draw_event_rect(event, rect)) text_rect = rect.Get() if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled(): text_rect = rect.Get() text_rect = (text_rect[0], text_rect[1] - Y_TEXT_OFFSET, text_rect[2], text_rect[3]) group.addElement( self._svg_clipped_text(event.text, text_rect, style, self._scene.center_text())) if event.has_data(): group.addElement(self._draw_contents_indicator(event, rect)) return group def _draw_event_rect(self, event, rect): boxBorderColor = self._get_event_border_color(event) if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled(): svg_rect = ShapeBuilder().createRect( rect.X, rect.Y - Y_RECT_OFFSET, rect.GetWidth(), rect.GetHeight() + Y_RECT_OFFSET, stroke=boxBorderColor, fill=self._get_event_box_color(event)) else: svg_rect = ShapeBuilder().createRect( rect.X, rect.Y, rect.GetWidth(), rect.GetHeight(), stroke=boxBorderColor, fill=self._get_event_box_color(event)) if self._shadow_flag: svg_rect.set_filter("url(#filterShadow)") return svg_rect def _draw_contents_indicator(self, event, rect): """ The data contents indicator is a small triangle drawn in the upper right corner of the event rectangle. """ corner_x = rect.X + rect.Width points = "%d,%d %d,%d %d,%d" % \ (corner_x - DATA_INDICATOR_SIZE, rect.Y, corner_x, rect.Y, corner_x, rect.Y + DATA_INDICATOR_SIZE) color = self._get_box_indicator_color(event) indicator = ShapeBuilder().createPolygon(points, fill=color, stroke=color) # TODO (low): Transparency ? return indicator def _svg_clipped_text(self, text, rect, style, center_text=False): group = G() group.set_clip_path("url(#%s)" % self._create_clip_path(rect)) group.addElement(self._draw_text(text, rect, style, center_text)) return group def _create_clip_path(self, rect): path_id, path = self._calc_clip_path(rect) clip = ClipPath() clip.addElement(path) clip.set_id(path_id) self._svg.addElement(self._create_defs(clip)) return path_id def _calc_clip_path(self, rect): rx, ry, width, height = rect if rx < 0: width += rx rx = 0 pathId = "path%d_%d_%d" % (rx, ry, width) p = Path(pathData="M %d %d H %d V %d H %d" % (rx, ry + height, rx + width, ry, rx)) return pathId, p def _draw_text(self, my_text, rect, style, center_text=False): my_text = self._encode_text(my_text) x, y = self._calc_text_pos(rect, center_text) label = Text(my_text, x, y) label.set_style(style.getStyle()) label.set_lengthAdjust("spacingAndGlyphs") return label def _calc_text_pos(self, rect, center_text=False): rx, ry, width, height = rect # In SVG, negative value should be OK, but they # are not drawn in Firefox. So add a special handling here. if rx < 0: width += rx x = 0 else: x = rx + INNER_PADDING if center_text: x += (width - 2 * INNER_PADDING) // 2 y = ry + height - INNER_PADDING return x, y def _text(self, the_text, x, y): encoded_text = self._encode_text(the_text) return Text(encoded_text, x, y) def _encode_text(self, text): return xmlescape(text) def _define_shadow_filter(self): return self._create_defs(self._get_shadow_filter()) def _create_defs(self, definition): d = Defs() d.addElement(definition) return d def _get_small_font_style(self): return self._get_font_style(SMALL_FONT_SIZE_PX, 'left', (2, 2)) def _get_small_centered_font_style(self): return self._get_font_style(SMALL_FONT_SIZE_PX, 'middle', (2, 2)) def _get_larger_font_style(self): return self._get_font_style(LARGER_FONT_SIZE_PX, 'left', "") def _get_font_style(self, size, anchor, dash_array): style = StyleBuilder() style.setStrokeDashArray(dash_array) style.setFontFamily(fontfamily="Verdana") style.setFontSize("%dpx" % size) style.setTextAnchor(anchor) return style def _get_shadow_filter(self): filterShadow = Filter(x="-.3", y="-.5", width=1.9, height=1.9) filtBlur = FeGaussianBlur(stdDeviation="4") filtBlur.set_in("SourceAlpha") filtBlur.set_result("out1") filtOffset = FeOffset() filtOffset.set_in("out1") filtOffset.set_dx(4) filtOffset.set_dy(-4) filtOffset.set_result("out2") filtMergeNode1 = FeMergeNode() filtMergeNode1.set_in("out2") filtMergeNode2 = FeMergeNode() filtMergeNode2.set_in("SourceGraphic") filtMerge = FeMerge() filtMerge.addElement(filtMergeNode1) filtMerge.addElement(filtMergeNode2) filterShadow.addElement( filtBlur ) # here i get an error from python. It is not allowed to add a primitive filter filterShadow.addElement(filtOffset) filterShadow.addElement(filtMerge) filterShadow.set_id("filterShadow") return filterShadow
def makePathSvg(self) -> Svg: """ Returns the main `Svg` object for Path view. """ viewbox = "0 0 %s %s" % (self.w, self.h) path_svg = Svg(width=self.w, height=self.h) path_svg.set_viewBox(viewbox) path_svg.set_preserveAspectRatio("xMinYMid meet") path_svg.setAttribute('id', "Cadnano_Path") # Main layer name path_svg.addElement(self.defs) path_svg.addElement(self.g_pathgridlines) # bottom layer path_svg.addElement(self.g_patholigos) path_svg.addElement(self.g_pathendpoints) path_svg.addElement(self.g_pathvirtualhelices) path_svg.addElement(self.g_pathvirtualhelixlabels) # top layer path_svg.addElement(self.g_pathinsertions) path_svg.addElement(self.g_pathskips) if self.cn_doc.sequence_applied: path_svg.addElement(self.g_pathsequences) else: print('No sequences were applied. Max oligo length: %s' % self.cn_doc.max_oligo_length, file=sys.stderr) path_svg.save(self.output_path) return path_svg
def makeSliceSvg(self) -> Svg: slice_svg = Svg(width=self.w, height=self.h) viewbox = "0 0 %s %s" % (self.w, self.h) slice_svg.set_viewBox(viewbox) slice_svg.set_preserveAspectRatio("xMidYMid meet") slice_svg.setAttribute('id', "Cadnano_Slice") # Main layer name slice_svg.addElement(self.g_slicevirtualhelices) # bottom layer slice_svg.addElement(self.g_slicevirtualhelixlabels) # top layer slice_svg.save(self.output_path)