def save_pie_chart(filename, all_angles, step_size, colors): # create the drawing surface svg_drawing = Drawing(filename=filename, size=(SVG_SIZE, SVG_SIZE), debug=True) start_x = SVG_SIZE // 2 start_y = SVG_SIZE // 2 radius = SVG_SIZE // 2 radians0 = all_angles[-1] for i in range(len(all_angles)): radians1 = all_angles[i] dx0 = radius * (math.sin(radians0)) dy0 = radius * (math.cos(radians0)) dx1 = radius * (math.sin(radians1)) dy1 = radius * (math.cos(radians1)) m0 = round(dy0, 9) n0 = round(-dx0, 9) m1 = round(-dy0 + dy1, 9) n1 = round(dx0 - dx1, 9) w = svg_drawing.path( d="M {0},{1} l {2},{3} a {4},{4} 0 0,0 {5},{6} z".format( start_x, start_y, m0, n0, radius, m1, n1), fill=colors[i], stroke="none", ) svg_drawing.add(w) radians0 = radians1 svg_drawing.save()
def main(): args = parser.parse_args() if not any([args.i, args.m]): print('Please, provide some integers or a text message to encode.\n') parser.print_help() quit(1) ints = args.i message = args.m ints_output_file = args.o or '-'.join([str(i) for i in ints]) + '.svg' message_output_file = args.o or validate_filename(message) + '.svg' if ints: d = Drawing(filename=ints_output_file, profile='tiny', size=(f'{10 + len(ints)*35}px', '70px')) d.add(draw_numbers(*ints)) d.save() if message: d = Drawing(filename=message_output_file, profile='tiny', size=(f'{10 + len(message)*35}px', '70px')) d.add(encode_message(message)) d.save()
def draw(self, start=0, end=None, ext='svg', name=None): if end is None: end = os.path.getsize(self.source_path) if name is None: name = self.source_path.name name = f"{name}.{ext}" if ext == 'svg': from svgwrite import Drawing from svgwrite.shapes import Rect dwg = Drawing(name, profile='tiny') elif ext == 'dxf': from dxfwrite import DXFEngine as dxf dwg = dxf.drawing(name) with open(self.source_path, 'rb') as source_file: for i, bit in enumerate(bits(source_file, start, end)): if bit == self.invert: x = (i // self.height) * self.scale y = (-i % self.height) * self.scale params = { 'insert': (x, y), 'size': (self.scale, self.scale) } if ext == 'dxf': rect = DxfRect(**params).to_face3d() else: rect = Rect(**params) dwg.add(rect) dwg.save()
class Writer(): def __init__(self, fontfn, outfn, size, pad=0, sw=0.2, nl=10): self.pos = (pad, pad) self.sw = sw self.pad = pad self.nl = nl self.xdst = 1 self.dwg = Drawing(str(outfn), size=size, profile='tiny', debug=False) with open(str(fontfn), 'r') as f: self.symbols = load(f)['symbols'] def newline(self): self.pos = (self.pad, self.pos[1] + self.nl) def write(self, phrase): for s in phrase: if s in self.symbols: o = self.symbols[s] gw = o['w'] paths = o['paths'] for path in paths: self.dwg.add( self.dwg.path(d=tosvgpath( list(shift_path(path, self.pos))), stroke=black, fill='none', stroke_width=self.sw)) self.pos = _rel_move(self.pos, (gw + self.xdst, 0)) else: print('symbol not found: {:s}'.format(s)) self.dwg.save(pretty=True, indent=2)
def test_jump_reduction(): paths = [] rect_width = 100 rect_height = rect_width / 2 for i in range(3): y_offset = rect_width*i*1j corners = [rect_height, rect_width+rect_height, rect_width+rect_height + rect_height*1j, rect_height*1j+ rect_height] corners = [c+y_offset for c in corners] lines = [Line(start=corners[j], end=corners[(j+1) % len(corners)]) for j in range(len(corners))] _path = Path(*lines) _path = _path.rotated(i*20) paths += list(_path) max_y = max([p.start.imag for p in paths]+[p.end.imag for p in paths]) max_x = max([p.start.real for p in paths]+[p.end.real for p in paths]) filename = "test_jump_reduction.svg" viewbox = [0, -rect_height, max_x+2*rect_height, max_y+2*rect_height] dwg = Drawing(filename, width="10cm", viewBox=" ".join([str(b) for b in viewbox])) dwg.add(dwg.path(d=Path(*paths).d())) dwg.save() dig = Digitizer() dig.filecontents = open(filename, "r").read() dig.svg_to_pattern() pattern_to_svg(dig.pattern, join(filename + ".svg"))
def slice_file(image_position_patient_array, image_orientation_patient, output_path=None, input_path=None): print("Status: Loading File.") model = STLModel(input_path) stats = model.stats() print(stats) print("Status: Calculating Slices") v1 = Point3D(image_orientation_patient[0][0], image_orientation_patient[0][1], image_orientation_patient[0][2]) v2 = Point3D(image_orientation_patient[1][0], image_orientation_patient[1][1], image_orientation_patient[1][2]) org = Point3D(0, 0, 0) for i, slice_loc in enumerate(image_position_patient_array): slice_loc = Point3D(slice_loc[0], slice_loc[1], slice_loc[2]) dwg = Drawing(output_path + str(i) + '.svg', profile='tiny') plane = Plane(v1 + slice_loc, v2 + slice_loc, org + slice_loc) x_axis = Line(org + slice_loc, v1 + slice_loc) y_axis = Line(org + slice_loc, v2 + slice_loc) pairs = model.slice_at_plane(plane, x_axis, y_axis) for pair in pairs: dwg.add(dwg.line(pair[0], pair[1], stroke=rgb(0, 0, 0, "%"))) dwg.save() print("Status: Finished Outputting Slices")
def raster_to_vector(image, path): drawing = Drawing(path, profile='tiny') width, height = image.size pixels = image.load() start_x = start_y = 0 last_pixel = pixels[0, 0] for close_y in range(height): for close_x in range(width): pixel = pixels[close_x, close_y] # If pixel has different color if pixel != last_pixel: color = _RGB.format(*last_pixel) _add_rectangles(drawing, width, color, start_x, start_y, close_x, close_y) # Reset values last_pixel = pixel start_x = close_x start_y = close_y _add_rectangles(drawing, width, color, start_x, start_y, close_x, close_y) # Save constructed SVG try: drawing.save() except FileNotFoundError: makedirs(dirname(path)) drawing.save()
def save_radial_tree_plot(filename, root_list, step_size): # define some params white = "rgb(255, 255, 255)" black = "rgb(0, 0, 0)" # create the drawing surface svg_drawing = Drawing( filename=filename, size=(SVG_SIZE, SVG_SIZE), debug=True) # create defs, in this case, just a single gradient rad_grad = svg_drawing.radialGradient(("50%", "50%"), "100%", ("50%", "50%"), id="rad_grad") rad_grad.add_stop_color("0%", black, 255) rad_grad.add_stop_color("100%", white, 255) svg_drawing.defs.add(rad_grad) tree_plot = svg_drawing.mask( id='treeplot', style='stroke: black; stroke-width: 3; fill: none; stroke-linecap: round; stroke-opacity: 0.5;') tree_plot.add(svg_drawing.rect( (0, 0), (SVG_SIZE, SVG_SIZE) ).fill(white)) for root in root_list: draw_radial_tree_node(svg_drawing, tree_plot, root, rad_grad, step_size) base_rect = svg_drawing.rect( (0, 0), (SVG_SIZE, SVG_SIZE), mask="url(#treeplot)").fill(black) svg_drawing.add(base_rect) svg_drawing.add(tree_plot) svg_drawing.save()
def generate_frame(i, j, k): filename = str(i)+str(j)+str(k)+".svg" dwg = Drawing(filename, size=(300, 300)) dwg.add(dwg.text('i = %d, j = %d, k %d' % (i, j, k), insert=(0.5, 20), fill='red')) dwg.add(dwg.line((0, 0), (10, 0), stroke=rgb(10, 10, 16, '%'))) dwg.save() pixel_width = 300 return convert(pixel_width, filename)
def test_scale(): scale = shape.scale(0.5) xml = ElementTree.tostring(scale.get_svg().get_xml()).decode() assert xml == ('<path d="M 50.000, 50.000 L 60.000, 70.000 ' 'L 80.000, 50.000 Z" />') drawing_scale = Drawing(dirname.joinpath('scale.svg'), size=(200, 200), stroke='black', stroke_width=1) drawing_scale.add(scale.get_svg()) drawing_scale.save()
def test_translate(): translate = shape.translate(-50, -50) xml = ElementTree.tostring(translate.get_svg().get_xml()).decode() assert xml == ('<path d="M 50.000, 50.000 L 70.000, 90.000 ' 'L 110.000, 50.000 Z" />') drawing_translate = Drawing(dirname.joinpath('translate.svg'), size=(200, 200), stroke='black', stroke_width=1) drawing_translate.add(translate.get_svg()) drawing_translate.save()
def test_flip(): flip = shape.flip(90) xml = ElementTree.tostring(flip.get_svg().get_xml()).decode() assert xml == ('<path d="M 80.000, 100.000 L 60.000, 140.000 ' 'L 20.000, 100.000 Z" />') drawing_flip = Drawing(dirname.joinpath('flip.svg'), size=(200, 200), stroke='black', stroke_width=1) drawing_flip.add(flip.get_svg()) drawing_flip.save()
def slice_file(f=None, resolution=0.1): print("Status: Loading File.") model = STLModel(f) scale = 10 stats = model.stats() sub_vertex = Vector3(stats['extents']['x']['lower'], stats['extents']['y']['lower'], stats['extents']['z']['lower']) add_vertex = Vector3(0.5, 0.5, 0.5) model.xmin = model.xmax = None model.ymin = model.ymax = None model.zmin = model.zmax = None print("Status: Scaling Triangles.") for triangle in model.triangles: triangle.vertices[0] -= sub_vertex triangle.vertices[1] -= sub_vertex triangle.vertices[2] -= sub_vertex # The lines above have no effect on the normal. triangle.vertices[0] = (triangle.vertices[0] * scale) + add_vertex triangle.vertices[1] = (triangle.vertices[1] * scale) + add_vertex triangle.vertices[2] = (triangle.vertices[2] * scale) + add_vertex # Recalculate the triangle normal u = model.triangles[0].vertices[1] - model.triangles[0].vertices[0] v = model.triangles[0].vertices[2] - model.triangles[0].vertices[0] triangle.n = Normal((u.y * v.z) - (u.z * v.y), (u.z * v.x) - (u.x * v.z), (u.x * v.y) - (u.y * v.x)) model.update_extents(triangle) print("Status: Calculating Slices") interval = scale * resolution stats = model.stats() print(stats) for targetz in range(0, int(stats['extents']['z']['upper']), int(interval)): dwg = Drawing('outputs/svg/' + str(targetz) + '.svg', profile='tiny') pairs = model.slice_at_z(targetz) for pair in pairs: dwg.add(dwg.line(pair[0], pair[1], stroke=rgb(0, 0, 0, "%"))) dwg.save() print("Status: Finished Outputting Slices")
def save(self): """Saves the illustration into :attr:`output_filename`.""" drawing = Drawing(self.output_filename, size=self.size, stroke=self.stroke, stroke_width=self.stroke_width, font_size=self.font_size, font_family=self.font_family) for region in self._regions: drawing.add(region.get_svg()) drawing.save(pretty=True)
class Builder(object): def __init__(self, name, commits, configuration, deltax=50.0, width=700.0, y=40.0, sep=20.0, image_size=40.0): self.commits = commits self.authors = AuthorList() self.initial_last_dates(configuration) self.position_function() height = y + sep + (self.max_elements + 1) * (image_size + sep) self.dwg = Drawing(name, size=(width + 2 * deltax, height)) self.deltax = deltax self.width = width self.y = y self.sep = sep self.image_size = image_size def initial_last_dates(self, configuration): self.initial = self.commits[0]["date"] self.last = self.commits[-1]["date"] self.max_elements = 0 for date_tuple, elements in configuration.items(): date = datetime(*date_tuple).replace(tzinfo=timezone.utc) self.last = max(self.last, date) self.initial = min(self.initial, date) self.max_elements = max(self.max_elements, len(elements)) def position_function(self): size = self.last.timestamp() - self.initial.timestamp() def position(date, y, deltax=0, deltay=0): return (self.deltax + (date.timestamp() - self.initial.timestamp()) / size * self.width + deltax, y + deltay) self.position = position def add(self, element): for xml in element.draw(self): self.dwg.add(xml) def save(self): self.dwg.save()
def draw_single_pentagon(self): pentagon = self.new_pentagon() dwg = Drawing("{}/single_pentagon.svg".format(self.output_folder), profile='tiny') dwg.add( dwg.path( **{ 'd': pentagon.d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) dwg.viewbox(self.pent_x, self.pent_y, self.pent_width, self.pent_height) dwg.save()
def save_radial_tree_plot(filename, root_list): svg_drawing = Drawing(filename=filename, size=(SVG_SIZE, SVG_SIZE), debug=True) tree_plot = svg_drawing.add( svg_drawing. g(id='treeplot', style= 'stroke: black; stroke-width: 1; fill: none; stroke-linecap: round;') ) for root in root_list: draw_radial_tree_node(svg_drawing, tree_plot, root) svg_drawing.save()
def draw(): drawing = Drawing(drawing_file_name, drawing_size) first_circle_view = \ Circle( center = first_circle_center, r = first_circle_radius, fill_opacity = 0, stroke = rgb(0, 0, 0), stroke_width = 1 ) first_circle_center_view = \ Circle( center = first_circle_center, r = 3, fill = rgb(0, 0, 0), stroke_width = 0 ) drawing.add(first_circle_view) drawing.add(first_circle_center_view) second_circle_view = \ Circle( center = second_circle_center, r = second_circle_radius, fill_opacity = 0, stroke = rgb(0, 0, 0), stroke_width = 1 ) second_circle_center_view = \ Circle( center = second_circle_center, r = 3, fill = rgb(0, 0, 0), stroke_width = 0 ) drawing.add(second_circle_view) drawing.add(second_circle_center_view) drawing.save()
def draw_paths(fn, bbox, paths, pad=(0, 0), sw=0.1): w, h = bbox sx, sy = pad dwg = Drawing(str(fn), size=(w + 2 * sx, h + 2 * sy), profile='tiny', debug=False) bbox_path = tosvgpath(_box(bbox), pad, closed=True) dwg.add(dwg.path(d=bbox_path, stroke=accent, stroke_width=sw, fill='none')) for path in paths: dwg.add( dwg.path(d=tosvgpath(path, pad), stroke=black, fill='none', stroke_width=sw)) dwg.save(pretty=True, indent=2)
class Writer(): def __init__(self, fontfn, outfn, size, pad=0, sw=0.2, nl=10, xdst=1): self.pos = (pad, pad) self.sw = sw self.pad = pad self.nl = nl self.xdst = xdst self.dwg = Drawing(str(outfn), size=size, profile='tiny', debug=False) with open(str(fontfn), 'r') as f: self.symbols = load(f)['symbols'] def newline(self): self.pos = (self.pad, self.pos[1] + self.nl) def scale(self, s): for o in self.symbols.values(): w = o['w'] h = o['h'] new_paths = [] for path in o['paths']: new_path = [] for x, y in path: new_path.append((s*(x-w*0.5)+w*0.5*s, s*(y-h*0.5)+h*0.5*s)) new_paths.append(new_path) o.update({'w': w*s, 'h': h*s, 'paths': new_paths}) return self def write(self, phrase): for s in phrase: if s in self.symbols: o = self.symbols[s] gw = o['w'] paths = o['paths'] for path in paths: self.dwg.add( self.dwg.path( d=tosvgpath(list(shift_path(path, self.pos))), stroke=black, fill='none', stroke_width=self.sw)) self.pos = _rel_move(self.pos, (gw + self.xdst, 0)) else: print('symbol not found: {:s}'.format(s)) self.dwg.save(pretty=True, indent=2)
def draw_diagram(self) -> None: n_countries: int = self.model.rowCount() if n_countries > 0: style: SvgStyle = self.svg_style delta_angle: float = 2.0*math.pi/n_countries max_value: float = max(self.model.values) dwg = Drawing(self.temp_svg_file.fileName(), profile='tiny', viewBox='-250 -250 500 500') for idx, v in enumerate(self.model.values): x: float = style.line_length * v/max_value * math.sin(idx * delta_angle) y: float = -style.line_length * v/max_value * math.cos(idx * delta_angle) dwg.add(shapes.Line(start=(0, 0), end=(x, y), stroke=style.line_color, stroke_width=style.line_width)) radius: float = style.circle_rad if style.circle_rad_normalization: radius *= v/max_value dwg.add(shapes.Circle(center=(x, y), r=radius, stroke=style.circle_stroke_color, stroke_width=style.circle_stroke_width, fill=style.circle_fill_color)) dwg.save(pretty=True) self.load_svg(self.temp_svg_file.fileName())
def export_svg_svgwrite(fn, paths, w, h, line_width=0.1): from svgwrite import Drawing w_str = "{}pt".format(w) h_str = "{}pt".format(h) dwg = Drawing(filename = fn, size = (w_str, h_str), viewBox=("0 0 {} {}".format(w,h))) for path in paths: if(len(path) > 1): str_list = [] str_list.append("M {},{}".format(path[0,0],path[0,1])) for e in path[1:]: str_list.append(" L {},{}".format(e[0],e[1])) s = ''.join(str_list) dwg.add(dwg.path(s).stroke(color="rgb(0%,0%,0%)",width=line_width).fill("none")) dwg.save()
def generate_tiling(self): dwg = Drawing("{}/tiling2.svg".format(self.output_folder), profile="tiny") current_color = 0 row_spacing = self.pent_height * 2 + self.bottom_length for y in range(self.num_down): transform = "translate({}, {})".format(0, self.rep_spacing * y) dgroup = dwg.add(dwg.g(transform=transform)) for x in range(self.num_across): # if x is odd, point 1 of pent 1 needs to be attached to point 3 of pent 2 if x % 2 == 1: dx = int( x / 2 ) * self.rep_spacing + self.pent_width * 2 + self.column_offset.real transform = "translate({}, {})".format( dx, self.column_offset.imag) else: transform = "translate({}, {})".format( int(x / 2) * self.rep_spacing, 0) group = dgroup.add(dwg.g(transform=transform)) for pent in self.cairo_group: group.add( dwg.path( **{ 'd': pent.d(), 'fill': self._colors[current_color % len(self._colors)], 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) current_color += 1 dwg.viewbox(*self.pattern_viewbox) dwg.save(pretty=True)
def export_svg_svgwrite(fn, paths, w, h, line_width=0.1): from svgwrite import Drawing w_str = "{}pt".format(w) h_str = "{}pt".format(h) dwg = Drawing(filename=fn, size=(w_str, h_str), viewBox=("0 0 {} {}".format(w, h))) for path in paths: if (len(path) > 1): str_list = [] str_list.append("M {},{}".format(path[0, 0], path[0, 1])) for e in path[1:]: str_list.append(" L {},{}".format(e[0], e[1])) s = ''.join(str_list) dwg.add( dwg.path(s).stroke(color="rgb(0%,0%,0%)", width=line_width).fill("none")) dwg.save()
def save_pie_chart(filename, root_list, step_size): # create the drawing surface svg_drawing = Drawing(filename=filename, size=(SVG_SIZE, SVG_SIZE), debug=True) start_x = SVG_SIZE // 2 start_y = SVG_SIZE // 2 radius = SVG_SIZE // 2 all_angles = [] for node in root_list: all_angles += node.pie_angle all_angles = sorted(all_angles) radians0 = all_angles[-1] for i in range(len(all_angles)): radians1 = all_angles[i] dx0 = radius * (math.sin(radians0)) dy0 = radius * (math.cos(radians0)) dx1 = radius * (math.sin(radians1)) dy1 = radius * (math.cos(radians1)) m0 = dy0 n0 = -dx0 m1 = -dy0 + dy1 n1 = dx0 - dx1 w = svg_drawing.path( d="M {0},{1} l {2},{3} a {4},{4} 0 0,0 {5},{6} z".format( start_x, start_y, m0, n0, radius, m1, n1), fill=colors[i], stroke="none", ) svg_drawing.add(w) radians0 = radians1 svg_drawing.save()
def draw_path_clip(self): path_filename = "{}/path_clip_{}.svg".format( self.output_folder, basename(self.filename).replace(".svg", "")) dwg = Drawing(path_filename) image_bbox = calc_overall_bbox(self.tile_paths) dx = self.pent_x - min(image_bbox[0], image_bbox[1]) dy = self.pent_y - min(image_bbox[2], image_bbox[3]) dwg.add( dwg.path( **{ "d": self.new_pentagon().d(), "fill": "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) neg_transform = "translate({}, {})".format(-dx, -dy) transform = "translate({}, {})".format(dx, dy) clip_path = dwg.defs.add( dwg.clipPath(id="pent_path", transform=neg_transform)) clip_path.add(dwg.path(d=self.new_pentagon().d())) group = dwg.add( dwg.g(clip_path="url(#pent_path)", transform=transform, id="clippedpath")) for i, path in enumerate(self.tile_paths): group.add( dwg.path(d=path.d(), style=self.tile_attributes[i].get('style'), id=self.tile_attributes[i]['id'])) dwg.add(dwg.use("#clippedpath", transform="transform(100, 100)")) dwg.viewbox(self.pent_x, self.pent_y, self.pent_width, self.pent_height) dwg.save() xml = xml.dom.minidom.parse(path_filename) open(path_filename, "w").write(xml.toprettyxml())
def make_x(wall: Wall, svg: Drawing): rngx = np.arange(2, 146 - 2, 0.25) waves = Polygon.Polygon() w2 = 0.85 for y in np.arange(80, 100 + 120, 5.0): points = [] for x in rngx: fxy = f(x + wall.x0, y / 1.66) points.append((x + wall.x0, y + fxy + w2)) for x in reversed(rngx): fxy = f(x + wall.x0, y / 1.66) points.append((x + wall.x0, y + fxy - w2)) p = Polygon.Polygon(points) waves += wall.window & p svg.save() waves &= wall.window wall.result = wall.wall - wall.window + waves
def make_y(wall: Wall, svg: Drawing): rngy = np.arange(wall.y0 - 2, wall.y1 + 2, 0.25) waves = Polygon.Polygon() w2 = 0.85 for x in np.arange(wall.x0 - 20, wall.x1 + 20, 6.0): points = [] for y in rngy: fxy = f(x, y / 1.66) points.append((x + fxy - w2, y)) for y in reversed(rngy): fxy = f(x, y / 1.66) points.append((x + fxy + w2, y)) p = Polygon.Polygon(points) waves += wall.window & p svg.save() waves &= wall.window wall.result = wall.wall - wall.window + waves
# Phys vs scaling indicator scaled_regions = [ (1, (2, 5)), (1, (7, 8)), (1, (11, 22)), (1, (24, 26)), (2, (2, 5)), (2, (7, 8)), (2, (11, 22)), (2, (24, 26)), (4, (2, 5)), (4, (7, 8)), (4, (11, 22)), (4, (24, 26)), (5, (2, 5)), (5, (7, 8)), (5, (11, 22)), (5, (24, 26)), (3, (17, 19)), ] for region in scaled_regions: x_line = vertical_locs[region[0]] start = base_y + region[1][0] * delta_y - 5 end = base_y + region[1][1] * delta_y - 5 line = dwg.line(start=(x_line, start), end=(x_line, end)) v_lines_scaled.add(line) dwg.save() print('done')
def export(self): dwg = Drawing(self.name, width=W, height=H) for r in self.rects: dwg.add(dwg.rect(insert=(r.x, r.y), size=(r.w, r.h), fill='black')) dwg.save()
def flatten_scene(pScene): lNode = pScene.GetRootNode() if not lNode: return for i in range(lNode.GetChildCount()): lChildNode = lNode.GetChild(i) if lChildNode.GetNodeAttribute() is None: continue lAttributeType = (lChildNode.GetNodeAttribute().GetAttributeType()) if lAttributeType != FbxNodeAttribute.eMesh: continue lMesh = lChildNode.GetNodeAttribute() projected_points = {} control_points = lMesh.GetControlPoints() start_point = 0 poly_paths = [] for polygon_num in range(lMesh.GetPolygonCount()): corners = [] for corner in range(3): corners.append(lMesh.GetPolygonVertex(polygon_num, corner)) # first, check if any of the control points are already projected flattened = [] for j, corner in enumerate(corners): if corner in projected_points: flattened.append(projected_points[corner]) continue target_corner = corners[j - 1] current_vec = control_points[corner] target_vec = control_points[target_corner] angle = acos( current_vec.DotProduct(target_vec) / (current_vec.Length() * target_vec.Length())) length = current_vec.Distance(target_vec) # find where the last point was. If it doesn't exist, use the start point start_corner = projected_points[target_corner] \ if target_corner in projected_points else start_point flattened_corner = start_corner + length * (cos(angle) + 1j * sin(angle)) projected_points[corner] = flattened_corner start_point = flattened_corner flattened.append(flattened_corner) poly_paths.append( Path(*[ Line(start=flattened[j], end=flattened[j - 1]) for j in range(3) ])) dwg = Drawing("mesh{}.svg".format(i), profile='tiny') for poly_path in poly_paths: dwg.add( dwg.path( **{ 'd': poly_path.d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) bbox = calc_overall_bbox(poly_paths) width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2]) dwg.viewbox(min(bbox[0], bbox[1]), min(bbox[2], bbox[3]), width, height) dwg.save()
def diode_svg_frame(illuminated, num_across=9, num_down=8, frame=0, single_route=-1): filename = "diode{:03d}.svg".format(frame) led_symbol = "resources/Symbol_LED.svg" image_width = 600 image_height = 400 right_margin = 30 bottom_margin = 30 dwg = Drawing(filename, size=(image_width+right_margin, image_height+bottom_margin), style="background-color:white") # create a white background rectangle dwg.add(dwg.rect(size=(image_width+right_margin, image_height+bottom_margin), insert=(0, 0), fill="white")) LED_dimensions = [106.0, 71.0] LED_points = [[35, 68], [35, 31], [66, 50]] LED_entries = [[4, 50], [103, 50]] aspect_ratio = LED_dimensions[1]/LED_dimensions[0] new_width = image_width/num_across new_height = new_width*aspect_ratio LED_scale = 0.75 LED_offsets = [new_width*LED_scale/2, new_height*LED_scale] junction_radius = 0.8 elements = [] for i in range(0, num_across): x_pos = new_width*(num_across-i-1) if illuminated[1] >= illuminated[0]: incoming_wire = illuminated[1] + 1 else: incoming_wire = illuminated[1] if i == incoming_wire: connection = "+" text_fill = "red" elif i == illuminated[0]: connection = "-" text_fill = "black" else: connection = "NC" text_fill = "gray" wire_label = "{} {}".format(i+1, connection) # the input wire title dwg.add(dwg.text(wire_label, insert=(x_pos+new_width-10, 10), fill=text_fill)) for j in range(0, num_down): y_pos = (image_height/num_down)*j position = [x_pos+LED_offsets[0], y_pos+LED_offsets[1]] scale = [LED_scale*new_width/LED_dimensions[0], LED_scale*new_height/LED_dimensions[1]] # the led svg dwg.add(dwg.image(led_symbol, insert=position, size=(new_width*LED_scale, new_height*LED_scale))) if i == illuminated[0] and j == illuminated[1] and single_route == -1: points = [] for point in LED_points: points.append(transform_point(point, scale, position)) # the illuminated svg box dwg.add(dwg.polygon(points=points, fill="yellow")) line_fill = "green" stroke_width = 1 insert_pos = -1 else: line_fill = "black" insert_pos = 0 stroke_width = 0.5 # for each LED, we want to generate a line going from the input # to its output entry_point = transform_point(LED_entries[0], scale, position) if i > j: incoming_line_points = [[new_width*(num_across-j)-LED_offsets[0], 0], [new_width*(num_across-j)-LED_offsets[0], y_pos+20], [entry_point[0], y_pos+20], entry_point] elif j > i: incoming_line_points = [ [new_width * (num_across - j - 1) - LED_offsets[0], 0], [new_width * (num_across - j - 1) - LED_offsets[0], entry_point[1] + LED_offsets[1]], [entry_point[0], entry_point[1]+LED_offsets[1]], entry_point] elif i == j: incoming_line_points = [ [new_width * (num_across - j - 1) - LED_offsets[0], 0], [new_width * (num_across - j - 1) - LED_offsets[0], entry_point[1]], entry_point] else: incoming_line_points = [] elements.insert(insert_pos, make_junction_line(dwg, incoming_line_points, junction_radius, line_fill, stroke_width)) # outgoing line exit_point = transform_point(LED_entries[1], scale, position) outgoing_line_points = [exit_point, [x_pos+new_width-LED_offsets[0], exit_point[1]], [x_pos+new_width-LED_offsets[0], 0]] elements.insert(insert_pos, make_junction_line(dwg, outgoing_line_points, junction_radius, line_fill, stroke_width)) route_points = [[new_width * (num_across - j - 1) - LED_offsets[0], 0]] for point in range(0, single_route+1): if point < i: route_points.append([new_width * (num_across - j - 1) - LED_offsets[0], 0]) # now create the network nodes = [] for i in range(0, num_across): for j in range(0, num_down): nodes.append(entry_point) nodes.append(exit_point) # flatten the elements structure elements = sum(elements, []) print(elements) # the lines should be drawn last so that they are layered on top of all # other elements #for element in elements: # dwg.add(element) dwg.save() return convert(image_width, filename)
def disvg(paths=None, colors=None, filename=os_path.join(getcwd(), 'disvg_output.svg'), stroke_widths=None, nodes=None, node_colors=None, node_radii=None, openinbrowser=True, timestamp=False, margin_size=0.1, mindim=600, dimensions=None, viewbox=None, text=None, text_path=None, font_size=None, attributes=None, svg_attributes=None, svgwrite_debug=False, paths2Drawing=False): """Takes in a list of paths and creates an SVG file containing said paths. REQUIRED INPUTS: :param paths - a list of paths OPTIONAL INPUT: :param colors - specifies the path stroke color. By default all paths will be black (#000000). This paramater can be input in a few ways 1) a list of strings that will be input into the path elements stroke attribute (so anything that is understood by the svg viewer). 2) a string of single character colors -- e.g. setting colors='rrr' is equivalent to setting colors=['red', 'red', 'red'] (see the 'color_dict' dictionary above for a list of possibilities). 3) a list of rgb 3-tuples -- e.g. colors = [(255, 0, 0), ...]. :param filename - the desired location/filename of the SVG file created (by default the SVG will be stored in the current working directory and named 'disvg_output.svg'). :param stroke_widths - a list of stroke_widths to use for paths (default is 0.5% of the SVG's width or length) :param nodes - a list of points to draw as filled-in circles :param node_colors - a list of colors to use for the nodes (by default nodes will be red) :param node_radii - a list of radii to use for the nodes (by default nodes will be radius will be 1 percent of the svg's width/length) :param text - string or list of strings to be displayed :param text_path - if text is a list, then this should be a list of path (or path segments of the same length. Note: the path must be long enough to display the text or the text will be cropped by the svg viewer. :param font_size - a single float of list of floats. :param openinbrowser - Set to True to automatically open the created SVG in the user's default web browser. :param timestamp - if True, then the a timestamp will be appended to the output SVG's filename. This will fix issues with rapidly opening multiple SVGs in your browser. :param margin_size - The min margin (empty area framing the collection of paths) size used for creating the canvas and background of the SVG. :param mindim - The minimum dimension (height or width) of the output SVG (default is 600). :param dimensions - The (x,y) display dimensions of the output SVG. I.e. this specifies the `width` and `height` SVG attributes. Note that these also can be used to specify units other than pixels. Using this will override the `mindim` parameter. :param viewbox - This specifies the coordinated system used in the svg. The SVG `viewBox` attribute works together with the the `height` and `width` attrinutes. Using these three attributes allows for shifting and scaling of the SVG canvas without changing the any values other than those in `viewBox`, `height`, and `width`. `viewbox` should be input as a 4-tuple, (min_x, min_y, width, height), or a string "min_x min_y width height". Using this will override the `mindim` parameter. :param attributes - a list of dictionaries of attributes for the input paths. Note: This will override any other conflicting settings. :param svg_attributes - a dictionary of attributes for output svg. :param svgwrite_debug - This parameter turns on/off `svgwrite`'s debugging mode. By default svgwrite_debug=False. This increases speed and also prevents `svgwrite` from raising of an error when not all `svg_attributes` key-value pairs are understood. :param paths2Drawing - If true, an `svgwrite.Drawing` object is returned and no file is written. This `Drawing` can later be saved using the `svgwrite.Drawing.save()` method. NOTES: * The `svg_attributes` parameter will override any other conflicting settings. * Any `extra` parameters that `svgwrite.Drawing()` accepts can be controlled by passing them in through `svg_attributes`. * The unit of length here is assumed to be pixels in all variables. * If this function is used multiple times in quick succession to display multiple SVGs (all using the default filename), the svgviewer/browser will likely fail to load some of the SVGs in time. To fix this, use the timestamp attribute, or give the files unique names, or use a pause command (e.g. time.sleep(1)) between uses. """ _default_relative_node_radius = 5e-3 _default_relative_stroke_width = 1e-3 _default_path_color = '#000000' # black _default_node_color = '#ff0000' # red _default_font_size = 12 # append directory to filename (if not included) if os_path.dirname(filename) == '': filename = os_path.join(getcwd(), filename) # append time stamp to filename if timestamp: fbname, fext = os_path.splitext(filename) dirname = os_path.dirname(filename) tstamp = str(time()).replace('.', '') stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext filename = os_path.join(dirname, stfilename) # check paths and colors are set if isinstance(paths, Path) or is_path_segment(paths): paths = [paths] if paths: if not colors: colors = [_default_path_color] * len(paths) else: assert len(colors) == len(paths) if isinstance(colors, str): colors = str2colorlist(colors, default_color=_default_path_color) elif isinstance(colors, list): for idx, c in enumerate(colors): if is3tuple(c): colors[idx] = "rgb" + str(c) # check nodes and nodes_colors are set (node_radii are set later) if nodes: if not node_colors: node_colors = [_default_node_color] * len(nodes) else: assert len(node_colors) == len(nodes) if isinstance(node_colors, str): node_colors = str2colorlist(node_colors, default_color=_default_node_color) elif isinstance(node_colors, list): for idx, c in enumerate(node_colors): if is3tuple(c): node_colors[idx] = "rgb" + str(c) # set up the viewBox and display dimensions of the output SVG # along the way, set stroke_widths and node_radii if not provided assert paths or nodes stuff2bound = [] if viewbox: if not isinstance(viewbox, str): viewbox = '%s %s %s %s' % viewbox if dimensions is None: dimensions = viewbox.split(' ')[2:4] elif dimensions: dimensions = tuple(map(str, dimensions)) def strip_units(s): return re.search(r'\d*\.?\d*', s.strip()).group() viewbox = '0 0 %s %s' % tuple(map(strip_units, dimensions)) else: if paths: stuff2bound += paths if nodes: stuff2bound += nodes if text_path: stuff2bound += text_path xmin, xmax, ymin, ymax = big_bounding_box(stuff2bound) dx = xmax - xmin dy = ymax - ymin if dx == 0: dx = 1 if dy == 0: dy = 1 # determine stroke_widths to use (if not provided) and max_stroke_width if paths: if not stroke_widths: sw = max(dx, dy) * _default_relative_stroke_width stroke_widths = [sw]*len(paths) max_stroke_width = sw else: assert len(paths) == len(stroke_widths) max_stroke_width = max(stroke_widths) else: max_stroke_width = 0 # determine node_radii to use (if not provided) and max_node_diameter if nodes: if not node_radii: r = max(dx, dy) * _default_relative_node_radius node_radii = [r]*len(nodes) max_node_diameter = 2*r else: assert len(nodes) == len(node_radii) max_node_diameter = 2*max(node_radii) else: max_node_diameter = 0 extra_space_for_style = max(max_stroke_width, max_node_diameter) xmin -= margin_size*dx + extra_space_for_style/2 ymin -= margin_size*dy + extra_space_for_style/2 dx += 2*margin_size*dx + extra_space_for_style dy += 2*margin_size*dy + extra_space_for_style viewbox = "%s %s %s %s" % (xmin, ymin, dx, dy) if dx > dy: szx = str(mindim) + 'px' szy = str(int(ceil(mindim * dy / dx))) + 'px' else: szx = str(int(ceil(mindim * dx / dy))) + 'px' szy = str(mindim) + 'px' dimensions = szx, szy # Create an SVG file if svg_attributes is not None: dimensions = (svg_attributes.get("width", dimensions[0]), svg_attributes.get("height", dimensions[1])) debug = svg_attributes.get("debug", svgwrite_debug) dwg = Drawing(filename=filename, size=dimensions, debug=debug, **svg_attributes) else: dwg = Drawing(filename=filename, size=dimensions, debug=svgwrite_debug, viewBox=viewbox) # add paths if paths: for i, p in enumerate(paths): if isinstance(p, Path): ps = p.d() elif is_path_segment(p): ps = Path(p).d() else: # assume this path, p, was input as a Path d-string ps = p if attributes: good_attribs = {'d': ps} for key in attributes[i]: val = attributes[i][key] if key != 'd': try: dwg.path(ps, **{key: val}) good_attribs.update({key: val}) except Exception as e: warn(str(e)) dwg.add(dwg.path(**good_attribs)) else: dwg.add(dwg.path(ps, stroke=colors[i], stroke_width=str(stroke_widths[i]), fill='none')) # add nodes (filled in circles) if nodes: for i_pt, pt in enumerate([(z.real, z.imag) for z in nodes]): dwg.add(dwg.circle(pt, node_radii[i_pt], fill=node_colors[i_pt])) # add texts if text: assert isinstance(text, str) or (isinstance(text, list) and isinstance(text_path, list) and len(text_path) == len(text)) if isinstance(text, str): text = [text] if not font_size: font_size = [_default_font_size] if not text_path: pos = complex(xmin + margin_size*dx, ymin + margin_size*dy) text_path = [Line(pos, pos + 1).d()] else: if font_size: if isinstance(font_size, list): assert len(font_size) == len(text) else: font_size = [font_size] * len(text) else: font_size = [_default_font_size] * len(text) for idx, s in enumerate(text): p = text_path[idx] if isinstance(p, Path): ps = p.d() elif is_path_segment(p): ps = Path(p).d() else: # assume this path, p, was input as a Path d-string ps = p # paragraph = dwg.add(dwg.g(font_size=font_size[idx])) # paragraph.add(dwg.textPath(ps, s)) pathid = 'tp' + str(idx) dwg.defs.add(dwg.path(d=ps, id=pathid)) txter = dwg.add(dwg.text('', font_size=font_size[idx])) txter.add(txt.TextPath('#'+pathid, s)) if paths2Drawing: return dwg # save svg if not os_path.exists(os_path.dirname(filename)): makedirs(os_path.dirname(filename)) dwg.save() # re-open the svg, make the xml pretty, and save it again xmlstring = md_xml_parse(filename).toprettyxml() with open(filename, 'w') as f: f.write(xmlstring) # try to open in web browser if openinbrowser: try: open_in_browser(filename) except: print("Failed to open output SVG in browser. SVG saved to:") print(filename)
def process(self): """ This takes a 4CAT results file as input, and outputs a plain text file containing all post bodies as one continuous string, sanitized. """ link_regex = re.compile(r"https?://[^\s]+") delete_regex = re.compile(r"[^a-zA-Z)(.,\n -]") # settings strip_urls = self.parameters.get("strip-urls", self.options["strip-urls"]["default"]) strip_symbols = self.parameters.get( "strip-symbols", self.options["strip-symbols"]["default"]) sides = self.parameters.get("sides", self.options["sides"]["default"]) self.align = self.parameters.get("align", self.options["align"]["default"]) window = convert_to_int( self.parameters.get("window", self.options["window"]["default"]), 5) + 1 query = self.parameters.get("query", self.options["query"]["default"]) self.limit = convert_to_int( self.parameters.get("limit", self.options["limit"]["default"]), 100) left_branches = [] right_branches = [] # do some validation if not query.strip() or re.sub(r"\s", "", query) != query: self.dataset.update_status( "Invalid query for word tree generation. Query cannot be empty or contain whitespace." ) self.dataset.finish(0) return window = min(window, self.options["window"]["max"] + 1) window = max(1, window) # find matching posts processed = 0 for post in self.iterate_csv_items(self.source_file): processed += 1 if processed % 500 == 0: self.dataset.update_status( "Processing and tokenising post %i" % processed) body = post["body"] if strip_urls: body = link_regex.sub("", body) if strip_symbols: body = delete_regex.sub("", body) body = word_tokenize(body) positions = [ i for i, x in enumerate(body) if x.lower() == query.lower() ] # get lists of tokens for both the left and right side of the tree # on the left side, all lists end with the query, on the right side, # they start with the query for position in positions: right_branches.append(body[position:position + window]) left_branches.append(body[max(0, position - window):position + 1]) # Some settings for rendering the tree later self.step = self.fontsize * 0.6 # approximately the width of a monospace char self.gap = (7 * self.step) # space for lines between nodes width = 1 # will be updated later # invert the left side of the tree (because that's the way we want the # branching to work for that side) # we'll visually invert the nodes in the tree again later left_branches = [list(reversed(branch)) for branch in left_branches] # first create vertical slices of tokens per level self.dataset.update_status("Generating token tree from posts") levels_right = [{} for i in range(0, window)] levels_left = [{} for i in range(0, window)] tokens_left = [] tokens_right = [] # for each "level" (each branching point representing a level), turn # tokens into nodes, record the max amount of occurences for any # token in that level, and keep track of what nodes are in which level. # The latter is needed because a token may occur multiple times, at # different points in the graph. Do this for both the left and right # side of the tree. for i in range(0, window): for branch in right_branches: if i >= len(branch): continue token = branch[i].lower() if token not in levels_right[i]: parent = levels_right[i - 1][branch[ i - 1].lower()] if i > 0 else None levels_right[i][token] = Node(token, parent=parent, occurrences=1, is_top_root=(parent is None)) tokens_right.append(levels_right[i][token]) else: levels_right[i][token].occurrences += 1 occurrences = levels_right[i][token].occurrences self.max_occurrences[i] = max( occurrences, self.max_occurrences[i] ) if i in self.max_occurrences else occurrences for branch in left_branches: if i >= len(branch): continue token = branch[i].lower() if token not in levels_left[i]: parent = levels_left[i - 1][branch[ i - 1].lower()] if i > 0 else None levels_left[i][token] = Node(token, parent=parent, occurrences=1, is_top_root=(parent is None)) tokens_left.append(levels_left[i][token]) else: levels_left[i][token].occurrences += 1 occurrences = levels_left[i][token].occurrences self.max_occurrences[i] = max( occurrences, self.max_occurrences[i] ) if i in self.max_occurrences else occurrences # nodes that have no siblings can be merged with their parents, else # the graph becomes unnecessarily large with lots of single-word nodes # connected to single-word nodes. additionally, we want the nodes with # the most branches to be sorted to the top, and then only retain the # most interesting (i.e. most-occurring) branches self.dataset.update_status("Merging and sorting tree nodes") for token in tokens_left: self.merge_upwards(token) self.sort_node(token) self.limit_subtree(token) for token in tokens_right: self.merge_upwards(token) self.sort_node(token) self.limit_subtree(token) # somewhat annoyingly, anytree does not simply delete nodes detached # from the tree in the previous steps, but makes them root nodes. We # don't need these root nodes (we only need the original root), so the # next step is to remove all root nodes that are not the main root. # We cannot modify a list in-place, so make a new list with the # relevant nodes level_sizes = {} filtered_tokens_right = [] for token in tokens_right: if token.is_root and not token.is_top_root: continue filtered_tokens_right.append(token) filtered_tokens_left = [] for token in tokens_left: if token.is_root and not token.is_top_root: continue filtered_tokens_left.append(token) # now we know which nodes are left, and can therefore determine how # large the canvas needs to be - this is based on the max number of # branches found on any level of the tree, in other words, the number # of "terminal nodes" height_left = self.whitespace * self.fontsize * max([ self.max_breadth(node) for node in filtered_tokens_left if node.is_top_root ]) height_right = self.whitespace * self.fontsize * max([ self.max_breadth(node) for node in filtered_tokens_right if node.is_top_root ]) height = max(height_left, height_right) canvas = Drawing(str(self.dataset.get_results_path()), size=(width, height), style="font-family:monospace;font-size:%ipx" % self.fontsize) # the nodes on the left side of the graph now have the wrong word order, # because we reversed them earlier to generate the correct tree # hierarchy - now reverse the node labels so they are proper language # again for token in tokens_left: self.invert_node_labels(token) wrapper = SVG(overflow="visible") self.dataset.update_status("Rendering tree to SVG file") if sides != "right": wrapper = self.render(wrapper, [ token for token in filtered_tokens_left if token.is_root and token.children ], height=height, side=self.SIDE_LEFT) if sides != "left": wrapper = self.render(wrapper, [ token for token in filtered_tokens_right if token.is_root and token.children ], height=height, side=self.SIDE_RIGHT) # things may have been rendered outside the canvas, in which case we # need to readjust the SVG properties wrapper.update({"x": 0 if self.x_min >= 0 else self.x_min * -1}) canvas.update({"width": (self.x_max - self.x_min)}) canvas.add(wrapper) canvas.save(pretty=True) self.dataset.update_status("Finished") self.dataset.finish(len(tokens_left) + len(tokens_right))
def disvg(paths=None, colors=None, filename=None, stroke_widths=None, nodes=None, node_colors=None, node_radii=None, openinbrowser=True, timestamp=None, margin_size=0.1, mindim=600, dimensions=None, viewbox=None, text=None, text_path=None, font_size=None, attributes=None, svg_attributes=None, svgwrite_debug=False, paths2Drawing=False, baseunit='px'): """Creates (and optionally displays) an SVG file. REQUIRED INPUTS: :param paths - a list of paths OPTIONAL INPUT: :param colors - specifies the path stroke color. By default all paths will be black (#000000). This paramater can be input in a few ways 1) a list of strings that will be input into the path elements stroke attribute (so anything that is understood by the svg viewer). 2) a string of single character colors -- e.g. setting colors='rrr' is equivalent to setting colors=['red', 'red', 'red'] (see the 'color_dict' dictionary above for a list of possibilities). 3) a list of rgb 3-tuples -- e.g. colors = [(255, 0, 0), ...]. :param filename - the desired location/filename of the SVG file created (by default the SVG will be named 'disvg_output.svg' or 'disvg_output_<timestamp>.svg' and stored in the temporary directory returned by `tempfile.gettempdir()`. See `timestamp` for information on the timestamp. :param stroke_widths - a list of stroke_widths to use for paths (default is 0.5% of the SVG's width or length) :param nodes - a list of points to draw as filled-in circles :param node_colors - a list of colors to use for the nodes (by default nodes will be red) :param node_radii - a list of radii to use for the nodes (by default nodes will be radius will be 1 percent of the svg's width/length) :param text - string or list of strings to be displayed :param text_path - if text is a list, then this should be a list of path (or path segments of the same length. Note: the path must be long enough to display the text or the text will be cropped by the svg viewer. :param font_size - a single float of list of floats. :param openinbrowser - Set to True to automatically open the created SVG in the user's default web browser. :param timestamp - if true, then the a timestamp will be appended to the output SVG's filename. This is meant as a workaround for issues related to rapidly opening multiple SVGs in your browser using `disvg`. This defaults to true if `filename is None` and false otherwise. :param margin_size - The min margin (empty area framing the collection of paths) size used for creating the canvas and background of the SVG. :param mindim - The minimum dimension (height or width) of the output SVG (default is 600). :param dimensions - The (x,y) display dimensions of the output SVG. I.e. this specifies the `width` and `height` SVG attributes. Note that these also can be used to specify units other than pixels. Using this will override the `mindim` parameter. :param viewbox - This specifies the coordinated system used in the svg. The SVG `viewBox` attribute works together with the the `height` and `width` attrinutes. Using these three attributes allows for shifting and scaling of the SVG canvas without changing the any values other than those in `viewBox`, `height`, and `width`. `viewbox` should be input as a 4-tuple, (min_x, min_y, width, height), or a string "min_x min_y width height". Using this will override the `mindim` parameter. :param attributes - a list of dictionaries of attributes for the input paths. Note: This will override any other conflicting settings. :param svg_attributes - a dictionary of attributes for output svg. :param svgwrite_debug - This parameter turns on/off `svgwrite`'s debugging mode. By default svgwrite_debug=False. This increases speed and also prevents `svgwrite` from raising of an error when not all `svg_attributes` key-value pairs are understood. :param paths2Drawing - If true, an `svgwrite.Drawing` object is returned and no file is written. This `Drawing` can later be saved using the `svgwrite.Drawing.save()` method. NOTES: * The `svg_attributes` parameter will override any other conflicting settings. * Any `extra` parameters that `svgwrite.Drawing()` accepts can be controlled by passing them in through `svg_attributes`. * The unit of length here is assumed to be pixels in all variables. * If this function is used multiple times in quick succession to display multiple SVGs (all using the default filename), the svgviewer/browser will likely fail to load some of the SVGs in time. To fix this, use the timestamp attribute, or give the files unique names, or use a pause command (e.g. time.sleep(1)) between uses. SEE ALSO: * document.py """ _default_relative_node_radius = 5e-3 _default_relative_stroke_width = 1e-3 _default_path_color = '#000000' # black _default_node_color = '#ff0000' # red _default_font_size = 12 if filename is None: timestamp = True if timestamp is None else timestamp filename = os_path.join(gettempdir(), 'disvg_output.svg') dirname = os_path.abspath(os_path.dirname(filename)) if not os_path.exists(dirname): makedirs(dirname) # append time stamp to filename if timestamp: fbname, fext = os_path.splitext(filename) tstamp = str(time()).replace('.', '') stfilename = os_path.split(fbname)[1] + '_' + tstamp + fext filename = os_path.join(dirname, stfilename) # check paths and colors are set if isinstance(paths, Path) or is_path_segment(paths): paths = [paths] if paths: if not colors: colors = [_default_path_color] * len(paths) else: assert len(colors) == len(paths) if isinstance(colors, str): colors = str2colorlist(colors, default_color=_default_path_color) elif isinstance(colors, list): for idx, c in enumerate(colors): if is3tuple(c): colors[idx] = "rgb" + str(c) # check nodes and nodes_colors are set (node_radii are set later) if nodes: if not node_colors: node_colors = [_default_node_color] * len(nodes) else: assert len(node_colors) == len(nodes) if isinstance(node_colors, str): node_colors = str2colorlist(node_colors, default_color=_default_node_color) elif isinstance(node_colors, list): for idx, c in enumerate(node_colors): if is3tuple(c): node_colors[idx] = "rgb" + str(c) # set up the viewBox and display dimensions of the output SVG # along the way, set stroke_widths and node_radii if not provided assert paths or nodes stuff2bound = [] if viewbox: if not isinstance(viewbox, str): viewbox = '%s %s %s %s' % viewbox if dimensions is None: dimensions = viewbox.split(' ')[2:4] elif dimensions: dimensions = tuple(map(str, dimensions)) def strip_units(s): return re.search(r'\d*\.?\d*', s.strip()).group() viewbox = '0 0 %s %s' % tuple(map(strip_units, dimensions)) else: if paths: stuff2bound += paths if nodes: stuff2bound += nodes if text_path: stuff2bound += text_path xmin, xmax, ymin, ymax = big_bounding_box(stuff2bound) dx = xmax - xmin dy = ymax - ymin if dx == 0: dx = 1 if dy == 0: dy = 1 # determine stroke_widths to use (if not provided) and max_stroke_width if paths: if not stroke_widths: sw = max(dx, dy) * _default_relative_stroke_width stroke_widths = [sw] * len(paths) max_stroke_width = sw else: assert len(paths) == len(stroke_widths) max_stroke_width = max(stroke_widths) else: max_stroke_width = 0 # determine node_radii to use (if not provided) and max_node_diameter if nodes: if not node_radii: r = max(dx, dy) * _default_relative_node_radius node_radii = [r] * len(nodes) max_node_diameter = 2 * r else: assert len(nodes) == len(node_radii) max_node_diameter = 2 * max(node_radii) else: max_node_diameter = 0 extra_space_for_style = max(max_stroke_width, max_node_diameter) xmin -= margin_size * dx + extra_space_for_style / 2 ymin -= margin_size * dy + extra_space_for_style / 2 dx += 2 * margin_size * dx + extra_space_for_style dy += 2 * margin_size * dy + extra_space_for_style viewbox = "%s %s %s %s" % (xmin, ymin, dx, dy) if mindim is None: szx = "{}{}".format(dx, baseunit) szy = "{}{}".format(dy, baseunit) else: if dx > dy: szx = str(mindim) + baseunit szy = str(int(ceil(mindim * dy / dx))) + baseunit else: szx = str(int(ceil(mindim * dx / dy))) + baseunit szy = str(mindim) + baseunit dimensions = szx, szy # Create an SVG file if svg_attributes is not None: dimensions = (svg_attributes.get("width", dimensions[0]), svg_attributes.get("height", dimensions[1])) debug = svg_attributes.get("debug", svgwrite_debug) dwg = Drawing(filename=filename, size=dimensions, debug=debug, **svg_attributes) else: dwg = Drawing(filename=filename, size=dimensions, debug=svgwrite_debug, viewBox=viewbox) # add paths if paths: for i, p in enumerate(paths): if isinstance(p, Path): ps = p.d() elif is_path_segment(p): ps = Path(p).d() else: # assume this path, p, was input as a Path d-string ps = p if attributes: good_attribs = {'d': ps} for key in attributes[i]: val = attributes[i][key] if key != 'd': try: dwg.path(ps, **{key: val}) good_attribs.update({key: val}) except Exception as e: warn(str(e)) dwg.add(dwg.path(**good_attribs)) else: dwg.add( dwg.path(ps, stroke=colors[i], stroke_width=str(stroke_widths[i]), fill='none')) # add nodes (filled in circles) if nodes: for i_pt, pt in enumerate([(z.real, z.imag) for z in nodes]): dwg.add(dwg.circle(pt, node_radii[i_pt], fill=node_colors[i_pt])) # add texts if text: assert isinstance(text, str) or (isinstance(text, list) and isinstance( text_path, list) and len(text_path) == len(text)) if isinstance(text, str): text = [text] if not font_size: font_size = [_default_font_size] if not text_path: pos = complex(xmin + margin_size * dx, ymin + margin_size * dy) text_path = [Line(pos, pos + 1).d()] else: if font_size: if isinstance(font_size, list): assert len(font_size) == len(text) else: font_size = [font_size] * len(text) else: font_size = [_default_font_size] * len(text) for idx, s in enumerate(text): p = text_path[idx] if isinstance(p, Path): ps = p.d() elif is_path_segment(p): ps = Path(p).d() else: # assume this path, p, was input as a Path d-string ps = p # paragraph = dwg.add(dwg.g(font_size=font_size[idx])) # paragraph.add(dwg.textPath(ps, s)) pathid = 'tp' + str(idx) dwg.defs.add(dwg.path(d=ps, id=pathid)) txter = dwg.add(dwg.text('', font_size=font_size[idx])) txter.add(txt.TextPath('#' + pathid, s)) if paths2Drawing: return dwg dwg.save() # re-open the svg, make the xml pretty, and save it again xmlstring = md_xml_parse(filename).toprettyxml() with open(filename, 'w') as f: f.write(xmlstring) # try to open in web browser if openinbrowser: try: open_in_browser(filename) except: print("Failed to open output SVG in browser. SVG saved to:") print(filename)
if end > start: ar_l = 10 else: ar_l = -10 ar_h = 7.5 pts = ((end-ar_l, y_mid+ar_h), (end, y_mid), (end-ar_l, y_mid-ar_h)) arrow = dwg.polyline(pts) h_lines.add(arrow) y += delta_y # Phys vs scaling indicator scaled_regions = [(0, (7, 9)), (1, (2, 11)), (1, (13, 14)), (2, (2, 11)), (2, (13, 14)), ] for region in scaled_regions: x_line = vertical_locs[region[0]] start = base_y + region[1][0] * delta_y - 5 end = base_y + region[1][1] * delta_y - 5 line = dwg.line(start=(x_line, start), end=(x_line, end)) v_lines_scaled.add(line) dwg.save() print('done')