def _create_curve_arrow(scene: Drawing, start: tuple, point1: tuple, point2: tuple, end: tuple, color: tuple): """Create an curved path (with cubic Bezier curve) around the given points in a scene. The path starts at `start` and ends at `end`. Points control_point1 and c2 are used as bezier control points. Args: scene (Scene): The scene where the path should be created. start: The start point. point1: The first control point. point2: The second control point. end: The end point. color: The arrow's color. Return: The modified scene """ middle = (point1[0] + (point2[0] - point1[0]) // 2, point1[1]) scene.add( Path(d=['M', start, 'C', point1, point1, middle], stroke=rgb(*color), stroke_width=1, fill='none')) scene.add( Path(d=['M', middle, 'C', point2, point2, end], stroke=rgb(*color), stroke_width=1, fill='none'))
def draw(self, svg_file): if self.intensity < self.filter_ratio: return lines = int(max(min(30 * self.intensity, 40), 1)) y_disp = float(self.block_size) / lines start = (self.left, self.top) end = (self.left, self.top) path = Path(("M", self.left, self.top), stroke="black", stroke_width="0.3", fill="none") # Stop when we hit the bottom while start[1] < self.bottom: # Determine which edge we are on # Left if start[0] == self.left: end = (self.right, start[1] + y_disp) # right elif start[0] == self.right: end = (self.left, start[1] + y_disp) if end[1] < self.bottom: # Recalc line to terminate at bottom path.push('L', *end) start = end svg_file.add(path)
def plot(*auts, filename='plot.svg', diagonal=True, endpoints=False, display_scale=1): assert len({aut.signature for aut in auts}) == 1 from svgwrite.path import Path from svgwrite.shapes import Line, Polyline from svgwrite.container import Group dwg, canvas = new_drawing(filename, display_scale) include_markers(dwg, endpoints) draw_grid(canvas, auts[0].signature) x_axis = Polyline([(0, 0), (SCALE, 0)], class_="axis") y_axis = Polyline([(0, 0), (0, SCALE)], class_="axis") canvas.add(x_axis) canvas.add(y_axis) if diagonal: diag = Line((0,0), (SCALE, SCALE), class_="grid depth0") canvas.add(diag) for i, aut in enumerate(auts): group = Group(class_="graph", id='graph_' + str(i)) canvas.add(group) last = (None, None) for (x0, y0, x1, y1) in graph_segments(aut): if last != (x0, y0): graph = Path(class_='graph_segment') group.add(graph) graph.push('M', SCALE * x0, SCALE * y0) graph.push('L', SCALE * x1, SCALE * y1) last = (x1, y1) dwg.save()
def draw(self, svg_file, max_lines=12): if self.intensity < self.filter_ratio: return count = int((max_lines * self.intensity) + 0.5) path = None for _ in range(count): start_side = random.choice((self.left, self.top)) offset = random.random() * self.block_size if start_side == self.left: x1 = self.left x2 = self.right y1 = self.top + offset y2 = self.bottom - offset else: x1 = self.left + offset x2 = self.right - offset y1 = self.top y2 = self.bottom if path is None: path = Path(("M", x1, y1), stroke="black", stroke_width="0.3", fill="none") else: path.push("M", x1, y1) path.push("L", x2, y2) svg_file.add(path)
def test_push_arc_2(self): p = Path('m0,0') p.push_arc(target=(7, 7), rotation=30, r=(2, 4), large_arc=False, angle_dir='-', absolute=True) self.assertEqual(p.tostring(), '<path d="m0,0 A 2 4 30 0,0 7 7" />')
def save(cls, image, filename, mosaic=False): # Use debug=False everywhere to turn off SVG validation, # which turns out to be obscenely expensive in this # library. DEBUG = False svg = svgwrite.Drawing(filename=filename, style='background-color: black;', size=(("%dpx" % (2 * image.r_outer), "%dpx" % (2 * image.r_outer))), debug=DEBUG) group = Group(debug=DEBUG) group.translate(image.r_outer, image.r_outer) for y, row in enumerate(image.pixels): ring = image.rings[y] theta = 2 * math.pi / len(row) r1 = ring.center + image.r_ring / 2 r2 = ring.center - image.r_ring / 2 for x, c in enumerate(row): if mosaic: path = Path(stroke='black', stroke_width=1, fill=cls.color_hex(c), debug=DEBUG) path.push( (('M', 0, r2), ('L', 0, r1), ('A', r1, r1, 0, '0,0', (r1 * sin(theta), r1 * cos(theta))), ('L', r2 * sin(theta), r2 * cos(theta)), ('A', r2, r2, 0, '0,1', (0, r2)))) else: path = Path(stroke=cls.color_hex(c), stroke_width=image.r_pixel, fill='none', debug=DEBUG) path.push((('M', 0, ring.center), ('A', ring.center, ring.center, 0, '0,0', (ring.center * sin(theta), ring.center * cos(theta))))) path.rotate(180 - degrees(theta * (x + 1)), center=(0, 0)) group.add(path) svg.add(group) svg.save()
def cubic_bezier(x1, x2, x3, x4, color='orange', under=False): if under: return Path('M {} {} C {} {} {} {} {} {}'.format(*x1, *x2, *x3, *x4), stroke=color, stroke_width='0.05', fill='none', stroke_dasharray='0.4') else: return Path('M {} {} C {} {} {} {} {} {}'.format(*x1, *x2, *x3, *x4), stroke=color, stroke_width='0.05', fill='none')
class SVG(object): ''' SVG ''' def __init__(self, id, WIDTH, HEIGHT): self.id = id self.WIDTH = WIDTH self.HEIGHT = HEIGHT x = random.randint(0, self.WIDTH) y = random.randint(0, self.HEIGHT) self.path = Path(d=('M', x, y)) self.elements = [] def addElement(self, newELement): self.elements.append(newELement) self.path.push(newELement) def getElements(self): return self.elements def getPointOfLastElement(self): point = [] #print(self.elements[-1]) point.append(self.elements[-1][-2]) point.append(self.elements[-1][-1]) return point def getPreviousPoints(self): points = [] for i in range(len(self.elements)): points.append([self.elements[i][-2], self.elements[i][-1]]) print("previous points: " + str(points)) return points def saveToFile(self): OUTPUT_DIR = "output" #Check if folder exists, if not then it will be created. path = "." + os.sep + OUTPUT_DIR + os.sep try: os.makedirs(path) except OSError: if os.path.exists(path): # We are nearly safe pass else: # There was an error on creation, so make sure we know about it raise dwg = svgwrite.Drawing(path + str(self.id) + '.svg', profile='tiny', size=(self.WIDTH, self.HEIGHT)) dwg.add(self.path) #print(dwg.tostring()) dwg.save()
def draw(self, svg_file, max_waves=20): if self.intensity < self.filter_ratio: return waves = int(max(min(max_waves * self.intensity, max_waves*1.5), 1)) peak_offset = self.block_size * 0.8 step_width = (self.block_size / (waves * 4.0)) path = Path(("M", self.left, self.mid_y), stroke="black", stroke_width="0.3", fill="none") for _ in range(waves): path.push("q", step_width, -peak_offset, 2 * step_width, 0) path.push("q", step_width, peak_offset, 2 * step_width, 0) svg_file.add(path)
def save(cls, image, filename, mosaic=False): # Use debug=False everywhere to turn off SVG validation, # which turns out to be obscenely expensive in this # library. DEBUG = False svg = svgwrite.Drawing(filename=filename, style='background-color: black;', size=(("%dpx" % (2 * image.r_outer), "%dpx" % (2 * image.r_outer))), debug=DEBUG) group = Group(debug=DEBUG) group.translate(image.r_outer, image.r_outer) for y, row in enumerate(image.pixels): ring = image.rings[y] theta = 2 * math.pi / len(row) r1 = ring.center + image.r_ring / 2 r2 = ring.center - image.r_ring / 2 for x, c in enumerate(row): if mosaic: path = Path(stroke='black', stroke_width=1, fill=cls.color_hex(c), debug=DEBUG) path.push((('M', 0, r2), ('L', 0, r1), ('A', r1, r1, 0, '0,0', (r1 * sin(theta), r1 * cos(theta))), ('L', r2 * sin(theta), r2 * cos(theta)), ('A', r2, r2, 0, '0,1', (0, r2)))) else: path = Path(stroke=cls.color_hex(c), stroke_width=image.r_pixel, fill='none', debug=DEBUG) path.push((('M', 0, ring.center), ('A', ring.center, ring.center, 0, '0,0', (ring.center * sin(theta), ring.center * cos(theta))))) path.rotate(180 - degrees(theta * (x + 1)), center=(0, 0)) group.add(path) svg.add(group) svg.save()
def test_constructor(self): p = Path(d="M 0 0", pathLength=100) self.assertEqual(p['pathLength'], 100) self.assertEqual(p.tostring(), '<path d="M 0 0" pathLength="100" />') # init path with command-string p = Path(d='M 10,7') self.assertEqual(p.tostring(), '<path d="M 10,7" />') # init path with a tuple of values p = Path(d=('M', 9, 9)) self.assertEqual(p.tostring(), '<path d="M 9 9" />')
def draw_name(drawing, x, y, node): name = getattr(node, "name", None) if name is not None: drawing.add( Text(name, insert=(x, y + NODE_RADIUS + SCALE), text_anchor="middle")) if EXPERIMENTAL: (start, end) = calc.calc_text_path(x, y) text_anchor = "text-anchor: start" if start[0] < WIDTH / 2: tmp = start start = end end = tmp text_anchor = "text-anchor: end" path_string = "M {} {} L {} {}".format(start[0], start[1], end[0], end[1]) p = Path(d=path_string) tp = TextPath(path=p, text=name, style=text_anchor) # TextPath has to be child of Text text = svgwrite.text.Text("") text.add(tp) drawing.add(p) drawing.add(text)
def load_death_symbol(drawing): # DEATH SYMBOL death_symbol = Group() death_symbol.add( Path(id="death", d="M 10 0 L 10 40 M 0 12 L 20 12", stroke_width="5")) drawing.defs.add(death_symbol) return death_symbol
def add_gausian_lines(self, column_pos, max_value, min_value, values): for gene in values: for category in values[gene]: gene_values = values[gene][category] if len(gene_values) > 1: stddev = statistics.stdev(gene_values) median = statistics.median(gene_values) colour = self.colour_helper.get_category_colour(category) scale_x = 1 scale_y = float(self.plottable_y) / (max_value - min_value) gausian_curve_path = self.calculate_gausian_curve( pos=column_pos[gene], height=30, stddev=stddev, scale_x=scale_x, scale_y=scale_y, horizontal=False, median=median, max_value=max_value, min_value=min_value) self.plot.add( Path(stroke=colour, stroke_width=2, stroke_linecap='round', stroke_opacity=0.5, fill=colour, fill_opacity=0.1, d=gausian_curve_path))
def make_svg_from_paths(dwg, paths_parsed): g_shape = dwg.g(class_="path") for p in paths_parsed: ps = Path(p.d()) print(p.d()) g_shape.add(ps) return g_shape
def draw(self, svg_file): if self.intensity < self.filter_ratio: return segments = int(max(min(20 * self.intensity, 30), 1)) def rand_point(): return random.uniform(self.left, self.right), random.uniform(self.top, self.bottom) start = rand_point() path = Path(("M",) + start, stroke="black", stroke_width="0.3", fill="none") for _ in range(segments): path.push("T", *rand_point()) svg_file.add(path)
def materialOutline(self, lines, layer=None): cfg = self.cfg if self.svg is not None: self.xOffset = 0.0 self.yOffset = cfg.dxfInput.ySize self.svg.add(Rect((0, 0), (cfg.dxfInput.xSize * self.pScale, \ cfg.dxfInput.ySize * self.pScale), \ fill='rgb(255, 255, 255)')) path = self.materialPath if path is None: self.materialPath = Path(stroke_width=.5, stroke='red', \ fill='none') path = self.materialPath for l in lines: (start, end) = l path.push('M', (self.scaleOffset(start))) path.push('L', (self.scaleOffset(end))) if self.d is not None: if layer is None: layer = self.lBorder for l in lines: (start, end) = l self.d.add(dxf.line(cfg.dxfInput.fix(start), \ cfg.dxfInput.fix(end), layer=layer))
def material(self, xSize, ySize): if self.svg is not None: self.offset = 0.0 path = self.materialPath if path is None: self.materialPath = Path(stroke_width=.5, stroke='red', \ fill='none') path = self.materialPath path.push('M', (self.scaleOffset((0, 0)))) path.push('L', (self.scaleOffset((xSize, 0)))) path.push('L', (self.scaleOffset((xSize, ySize)))) path.push('L', (self.scaleOffset((0, ySize)))) path.push('L', (self.scaleOffset((0, 0)))) self.path.push('M', (self.scaleOffset((0, 0)))) # dwg = svgwrite.Drawing(name, (svg_size_width, svg_size_height), \ # debug=True) cfg = self.cfg if self.d is not None: orientation = cfg.orientation if orientation == O_UPPER_LEFT: p0 = (0.0, 0.0) p1 = (xSize, 0.0) p2 = (xSize, -ySize) p3 = (0.0, -ySize) elif orientation == O_LOWER_LEFT: p0 = (0.0, 0.0) p1 = (xSize, 0.0) p2 = (xSize, ySize) p3 = (0.0, ySize) elif orientation == O_UPPER_RIGHT: p0 = (0.0, 0.0) p1 = (-xSize, 0.0) p2 = (-xSize, -ySize) p3 = (0.0, -ySize) elif orientation == O_LOWER_RIGHT: p0 = (0.0, 0.0) p1 = (-xSize, 0.0) p2 = (-xSize, ySize) p3 = (0.0, ySize) elif orientation == O_CENTER: p0 = (-xSize / 2, -ySize / 2) p1 = (xSize / 2, -ySize / 2) p2 = (xSize / 2, ySize / 2) p3 = (-xSize / 2, ySize / 2) elif orientation == O_POINT: dxfInput = cfg.dxfInput p0 = (dxfInput.xMin, dxfInput.yMin) p1 = (dxfInput.xMin, dxfInput.yMax) p2 = (dxfInput.xMax, dxfInput.yMax) p3 = (dxfInput.xMax, dxfInput.yMin) else: ePrint("invalid orientation") self.d.add(dxf.line(p0, p1, layer=self.lBorder)) self.d.add(dxf.line(p1, p2, layer=self.lBorder)) self.d.add(dxf.line(p2, p3, layer=self.lBorder)) self.d.add(dxf.line(p3, p0, layer=self.lBorder))
def __init__(self, id, WIDTH, HEIGHT): self.id = id self.WIDTH = WIDTH self.HEIGHT = HEIGHT x = random.randint(0, self.WIDTH) y = random.randint(0, self.HEIGHT) self.path = Path(d=('M', x, y)) self.elements = []
def _define_one_symbol(self): dot = Symbol(id="dot") dot.add(Circle(center=(0, 0), r=self.dimens.stroke_width * 0.4, fill="black", stroke="none")) self.dwg.defs.add(dot) one = Symbol(id="one", class_="bit-1", stroke_width=1, stroke="black") one.add(Circle(center=(0, 0), r=self.dimens.bit_radius, fill="none", stroke="none")) empty_face_symbol = Symbol(id="empty_face") light_face_symbol = Symbol(id="light_face") dense_face_symbol = Symbol(id="dense_face") points = ["{} {}".format( self.dimens.bit_radius * math.sin(2 * math.pi * v / self.poly.num_vertices), self.dimens.bit_radius * math.cos(2 * math.pi * v / self.poly.num_vertices) ) for v in range(0, 3)] data = ['M', "0 0 L", *points, 'Z'] path = Path(data, fill="none", stroke_linejoin="bevel") empty_face_symbol.add(path) light_face_symbol.add(path) dense_face_symbol.add(path) x0 = 0 x1 = self.dimens.bit_radius * math.sin(2 * math.pi * 2 / self.poly.num_vertices) y0 = self.dimens.bit_radius * math.cos(2 * math.pi * 2 / self.poly.num_vertices) y1 = self.poly.outer_circle_radius * self.dimens.bit_radius w = x1 - x0 h = y1 - y0 h_rect = y1 num_dense = 200 num_light = int(num_dense / 2) points = np.apply_along_axis( lambda p: np.array([p[0] * w, p[1] * h_rect + p[0] * y0]), 1, np.clip( i4_sobol_generate(2, num_dense) + np.random.rand(num_dense, 2) / h, 0, 1 )) for p in points[:num_light]: light_face_symbol.add(Use(dot.get_iri(), (p[0], p[1]))) dense_face_symbol.add(Use(dot.get_iri(), (p[0], p[1]))) for p in points[num_light:]: dense_face_symbol.add(Use(dot.get_iri(), (p[0], p[1]))) self.dwg.defs.add(empty_face_symbol) self.dwg.defs.add(light_face_symbol) self.dwg.defs.add(dense_face_symbol) f1 = Use(empty_face_symbol.get_iri()) one.add(f1) f2 = Use(dense_face_symbol.get_iri()) f2.rotate(120) one.add(f2) f3 = Use(light_face_symbol.get_iri()) f3.rotate(240) one.add(f3) self.dwg.defs.add(one)
def __init__(self): super().__init__() rect = Rect(insert=(0, 0), size=(5, 5), fill='#9e9e9e') path = Path('M 0 5 L 5 0 Z M 6 4 L 4 6 Z M -1 1 L 1 -1 Z', stroke='#888', stroke_width=1) pattern = Pattern(id=self.__class__.__name__.lower(), patternUnits='userSpaceOnUse', size=(5, 5), stroke='none') pattern.add(rect) pattern.add(path) self.add(pattern)
def open(self, inFile, drawDxf=True, drawSvg=True): if drawSvg and self.svg is None: svgFile = inFile + ".svg" try: self.svg = Drawing(svgFile, profile='full', fill='black') self.path = Path(stroke_width=.5, stroke='black', fill='none') except IOError: self.svg = None self.path = None ePrint("svg file open error %s" % (svgFile)) if drawDxf and self.d is None: dxfFile = inFile + "_ngc.dxf" try: self.d = dxf.drawing(dxfFile) self.layerIndex = 0 self.d.add_layer('0', color=self.color, lineweight=0) self.setupLayers() except IOError: self.d = None ePrint("dxf file open error %s" % (dxfFile))
def arc(center): center = center arc_start = 150 arc_end = 30 radius = center[2] #p= Path(d=f"M {center[0]} {center[1]}") p = Path(d=[]) current_a = (-(radius * math.cos(math.pi * (arc_start / 180.0))) + center[0], -(radius * math.sin(math.pi * (arc_start / 180.0))) + center[1]) #p.push(f"M {(radius * math.cos(math.pi *arc_start/180.0 )) +center[0] } {(radius * math.sin(math.pi * arc_start/180.0)) +center[1] } ") #p.push(f"M 0 0 L 0 0 {center[0]} {center[1]} ") p.push(f"M {current_a[0]} {current_a[1]} ") target = (-(radius * math.cos(math.pi * (arc_end / 180.1))) + center[0], -(radius * math.sin(math.pi * (arc_end / 180.1))) + center[1]) p.push_arc(target, rotation=0, r=radius, large_arc=False, angle_dir='-', absolute=True) #p.push(f" L {target[0]} {target[1]} ") #p.push(f"L {target[0]} {target[1]} {center[0]} {center[1]}") svg_entity = svgwrite.Drawing().path(d=p.commands, stroke="blue", stroke_width="1", fill="none") ergebnis = svg_entity._repr_svg_() return ergebnis
def drawLayer(self, paths, fillColor='black', fillOpacity=0.5, strokeColor='black', strokeWidth=1, strokeOpacity=1.0): for path in paths: _polygon = self.svgFile.add(Path(path)) _polygon.fill(fillColor, opacity=fillOpacity) _polygon.stroke(strokeColor, width=strokeWidth, opacity=strokeOpacity)
def draw_slice(center, radius, start_angle, stop_angle, **kwargs): p_a = Path(**kwargs) angle = math.radians(stop_angle - start_angle) / 2.0 p_a.push(f"""M {center[0]} {center[1]} l {cos(-1*angle)*radius} {sin(-1*angle)*radius}""") p_a.push_arc( target=(cos(angle) * radius + center[0], sin(angle) * radius + center[1]), rotation=0, r=radius, large_arc=True if stop_angle - start_angle > 180 else False, angle_dir="+", absolute=True, ) p_a.push("Z") p_a.rotate( angle=(min([start_angle, stop_angle]) + (stop_angle - start_angle) / 2.0), center=center, ) return p_a
def draw_line(scene: Drawing, start: tuple, ctrl1: tuple, ctrl2: tuple, end: tuple, is_curved: bool, edge_color: tuple): if is_curved: # cubic Bezier curve scene.add( Path(d=['M', start, 'C', ctrl1, ctrl2, end], stroke=rgb(*edge_color), stroke_width=1, fill='none')) else: scene.add( Line(start, end, shape_rendering='inherit', stroke=rgb(*edge_color), stroke_width=1))
def draw(self): lenghts = self.config['timingdiagram']['lengths'] styles = self.config['timingdiagram'] seq = self.states grp = Group() lastframe = len( seq) * lenghts.FRAME_LENGTH + 2 * lenghts.BEGIN_MARGIN_FRAME Y_AXES_TOP = (lenghts.Y_AXE_STARTX, lenghts.Y_AXE_TOP_MARGIN) Y_AXES_BOT = (lenghts.Y_AXE_STARTX, lenghts.Y_AXE_TOP_MARGIN + lenghts.Y_AXE_HEIGHT) Y_AXES_END = (lenghts.Y_AXE_STARTX + lastframe, lenghts.Y_AXE_TOP_MARGIN + lenghts.Y_AXE_HEIGHT) grp.add(Line(Y_AXES_TOP, Y_AXES_BOT, **styles.axes.d)) grp.add(Line(Y_AXES_BOT, Y_AXES_END, **styles.axes.d)) grp.add( Line((lenghts.Y_AXE_STARTX - 2, lenghts.TOP_VALUE_LINE), (lenghts.Y_AXE_STARTX + lastframe, lenghts.TOP_VALUE_LINE), **styles.top_line.d)) grp.add( Line((lenghts.Y_AXE_STARTX - 2, lenghts.BOT_VALUE_LINE), (lenghts.Y_AXE_STARTX + lastframe, lenghts.BOT_VALUE_LINE), **styles.bottom_line.d)) grp.add( Text("true", (lenghts.WIDTH_VARIABLE_NAME, lenghts.TOP_VALUE_LINE), **styles.valuename.d)) grp.add( Text("false", (lenghts.WIDTH_VARIABLE_NAME, lenghts.BOT_VALUE_LINE), **styles.valuename.d)) grp.add( Text( self.name, (lenghts.WIDTH_VARIABLE_NAME - 10, lenghts.BOT_VALUE_LINE - 5), **styles.varname.d)) grp.add(Path(self._build_curve(), **styles.curve.d)) return grp
def get_svg(self, unit=mm): ''' Generate an SVG Drawing based of the generated gear profile. :param unit: None or a unit within the 'svgwrite' module, such as svgwrite.mm, svgwrite.cm :return: An svgwrite.Drawing object populated only with the gear path. ''' points = self.get_point_list() width, height = np.ptp(points, axis=0) left, top = np.min(points, axis=0) size = (width * unit, height * unit) if unit is not None else (width, height) dwg = Drawing(size=size, viewBox='{} {} {} {}'.format(left, top, width, height)) p = Path('M') p.push(points) p.push('Z') dwg.add(p) return dwg
def trans_arc(dxf_entity): radius= dxf_entity.dxf.radius #stroke = dxf_entity.dxf.color center = slice_l2(dxf_entity.dxf.center) arc_start = dxf_entity.dxf.start_angle arc_end = dxf_entity.dxf.end_angle p=Path(d=[]) radius = dxf_entity.dxf.radius current_a = (-(radius * math.cos(math.pi * (arc_start/180.0 ))) +center[0], -(radius * math.sin(math.pi * (arc_start/180.0))) +center[1]) #p.push(f"M {current_a[0]} {current_a[1]} L {center[0]} {center[1]} {current_a[0]} {current_a[1]} ") p.push(f"M {current_a[0]} {current_a[1]} ") target=( -(radius * math.cos(math.pi * (arc_end/180.1) )) +center[0], -(radius * math.sin(math.pi * (arc_end/180.1)))+center[1] ) p.push_arc(target, rotation=0, r=radius, large_arc=False , angle_dir='-', absolute=True) #p.push(f" L {target[0]} {target[1]} ") #p.push(f"L {target[0]} {target[1]} {center[0]} {center[1]}") #print(f"trans_arc: dxf_entity.dxf.start_angle= {dxf_entity.dxf.start_angle} dxf_entity.dxf.end_angle={dxf_entity.dxf.end_angle} circle_center={circle_center},dxf_entity.dxf.center= {dxf_entity.dxf.center}") #print(f"trans_arc: dxf_entity.dxf.radius= {dxf_entity.dxf.radius}") #svg_entity = svgwrite.Drawing().circle(center=circle_center, r=0, stroke =stroke , fill="none", stroke_width = thickness)# !!! #svg_entity = svgwrite.Drawing().arc(center=circle_center, r=circle_radius, stroke =stroke , fill="none", stroke_width = thickness) svg_entity = svgwrite.Drawing().path(d=p.commands,stroke=stroke, stroke_width=thickness ,fill="none") # ->src/python/svgwrite/svgwrite/path.py print(f"p.commands= {svg_entity._repr_svg_()}") #svg_entity = svgwrite.Drawing().arc(center=circle_center, r=circle_radius, stroke =stroke , fill="none", stroke_width = thickness) svg_entity.scale(SCALE,-SCALE) return svg_entity
def render(self, canvas, level, x=0, y=0, origin=None, height=None, side=1, init=True, level_index=0): """ Render node set to canvas :param canvas: SVG object :param list level: List of nodes to render :param int x: X coordinate of top left of level block :param int y: Y coordinate of top left of level block :param tuple origin: Coordinates to draw 'connecting' line to :param float height: Block height budget :param int side: What direction to move into: 1 for rightwards, -1 for leftwards :param bool init: Whether the draw the top level of nodes. Only has an effect if side == self.SIDE_LEFT :return: Updated canvas """ if not level: return canvas # this eliminates a small misalignment where the left side of the # graph starts slightly too far to the left if init and side == self.SIDE_LEFT: x += self.step # determine how many nodes we'll need to fit on top of each other # within this block required_space_level = sum([self.max_breadth(node) for node in level]) # draw each node and the tree below it for node in level: # determine how high this block will be based on the available # height and the nodes we'll need to fit in it required_space_node = self.max_breadth(node) block_height = (required_space_node / required_space_level) * height # determine how much we want to enlarge the text occurrence_ratio = node.occurrences / self.max_occurrences[ level_index] if occurrence_ratio >= 0.75: embiggen = 3 elif occurrence_ratio > 0.5: embiggen = 2 elif occurrence_ratio > 0.25: embiggen = 1.75 elif occurrence_ratio > 0.15: embiggen = 1.5 else: embiggen = 1 # determine how large the text block will be (this is why we use a # monospace font) characters = len(node.name) text_width = characters * self.step text_width *= (embiggen * 1) text_offset_y = self.fontsize if self.align == "top" else ( (block_height) / 2) # determine where in the block to draw the text and where on the # canvas the block appears block_position = (x, y) block_offset_x = -(text_width + self.step) if side == self.SIDE_LEFT else 0 self.x_min = min(self.x_min, block_position[0] + block_offset_x) self.x_max = max(self.x_max, block_position[0] + block_offset_x + text_width) # the first node on the left side of the graph does not need to be # drawn if the right side is also being drawn because in that case # it's already going to be included through that part of the graph if not (init and side == self.SIDE_LEFT): container = SVG(x=block_position[0] + block_offset_x, y=block_position[1], width=text_width, height=block_height, overflow="visible") container.add( Text(text=node.name, insert=(0, text_offset_y), alignment_baseline="middle", style="font-size:" + str(embiggen) + "em")) canvas.add(container) else: # adjust position to make left side connect to right side x += text_width block_position = (block_position[0] + text_width, block_position[1]) # draw the line connecting this node to the parent node if origin: destination = (x - self.step, y + text_offset_y) # for the left side of the graph, draw a curve leftwards # instead of rightwards if side == self.SIDE_RIGHT: bezier_origin = origin bezier_destination = destination else: bezier_origin = (destination[0] + self.step, destination[1]) bezier_destination = (origin[0] - self.step, origin[1]) # bezier curve control points control_x = bezier_destination[0] - ( (bezier_destination[0] - bezier_origin[0]) / 2) control_left = (control_x, bezier_origin[1]) control_right = (control_x, bezier_destination[1]) # draw curve flow = Path(stroke="#000", fill_opacity=0, stroke_width=1.5) flow.push("M %f %f" % bezier_origin) flow.push("C %f %f %f %f %f %f" % tuple( [*control_left, *control_right, *bezier_destination])) canvas.add(flow) # bezier curves for the next set of nodes will start at these # coordinates new_origin = (block_position[0] + ((text_width + self.step) * side), block_position[1] + text_offset_y) # draw this node's children canvas = self.render(canvas, node.children, x=x + ((text_width + self.gap) * side), y=y, origin=new_origin, height=int(block_height), side=side, init=False, level_index=level_index + 1) y += block_height return canvas
def nest(output, files, wbin, hbin, enclosing_rectangle=False): packer = newPacker() def float2dec(x): return _float2dec(x, 4) def bbox_paths(paths): bbox = None for p in paths: p_bbox = p.bbox() if bbox is None: bbox = p_bbox else: bbox = (min(p_bbox[0], bbox[0]), max(p_bbox[1], bbox[1]), min(p_bbox[2], bbox[2]), max(p_bbox[3], bbox[3])) return tuple(float2dec(x) for x in bbox) all_paths = {} for svg\ in files: paths, attributes = svg2paths(svg) bbox = bbox_paths(paths) for i in range(files[svg]): rid = svg + str(i) all_paths[rid] = {'paths': paths, 'bbox': bbox} print(rid) packer.add_rect(bbox[1] - bbox[0], bbox[3] - bbox[2], rid=rid) print('Rectangle packing...') while True: packer.add_bin(wbin, hbin) packer.pack() rectangles = {r[5]: r for r in packer.rect_list()} if len(rectangles) == len(all_paths): break else: print('not enough space in the bin, adding ') combineds = {} print('packing into SVGs...') for rid, obj in all_paths.items(): paths = obj['paths'] bbox = obj['bbox'] group = Group() width, height = (float2dec(bbox[1] - bbox[0]), float2dec(bbox[3] - bbox[2])) bin, x, y, w, h, _ = rectangles[rid] if bin not in combineds: svg_file = output if bin != 0: splitext = os.path.splitext(svg_file) svg_file = splitext[0] + '.%s' % bin + splitext[1] dwg = Drawing(svg_file, profile='tiny', size=('%smm' % wbin, '%smm' % hbin), viewBox="0 0 %s %s" % (wbin, hbin)) combineds[bin] = dwg combined = combineds[bin] if (width > height and w > h) or \ (width < height and w < h) or \ (width == height and w == h): rotate = 0 dx = -bbox[0] dy = -bbox[2] else: rotate = 90 dx = -bbox[2] dy = -bbox[0] for p in paths: path = Path(d=p.d()) path.stroke(color='red', width='1') path.fill(opacity=0) group.add(path) group.translate(x + dx, y + dy) group.rotate(rotate) combined.add(group) for combined in combineds.values(): if enclosing_rectangle: r = Rect(size=(wbin, hbin)) r.fill(opacity=0) r.stroke(color='lightgray') combined.add(r) print('SVG saving...') combined.save(pretty=True)
def process(self): graphs = {} intervals = [] smooth = self.parameters.get("smooth") normalise_values = self.parameters.get("normalise") completeness = convert_to_int(self.parameters.get("complete"), 0) graph_label = self.parameters.get("label") top = convert_to_int(self.parameters.get("top"), 10) # first gather graph data: each distinct item gets its own graph and # for each graph we have a sequence of intervals, each interval with # its own value first_date = "9999-99-99" last_date = "0000-00-00" for row in self.iterate_items(self.source_file): if row["item"] not in graphs: graphs[row["item"]] = {} # make sure the months and days are zero-padded interval = row.get("date", "") interval = "-".join([ str(bit).zfill(2 if len(bit) != 4 else 4) for bit in interval.split("-") ]) first_date = min(first_date, interval) last_date = max(last_date, interval) if interval not in intervals: intervals.append(interval) if interval not in graphs[row["item"]]: graphs[row["item"]][interval] = 0 graphs[row["item"]][interval] += float(row.get("value", 0)) # first make sure we actually have something to render intervals = sorted(intervals) if len(intervals) <= 1: self.dataset.update_status( "Not enough data for a side-by-side over-time visualisation.") self.dataset.finish(0) return # only retain most-occurring series - sort by sum of all frequencies if len(graphs) > top: selected_graphs = { graph: graphs[graph] for graph in sorted( graphs, key=lambda x: sum( [graphs[x][interval] for interval in graphs[x]]), reverse=True)[0:top] } graphs = selected_graphs # there may be items that do not have values for all intervals # this will distort the graph, so the next step is to make sure all # graphs consist of the same continuous interval list missing = {graph: 0 for graph in graphs} for graph in graphs: missing[graph], graphs[graph] = pad_interval( graphs[graph], first_interval=first_date, last_interval=last_date) # now that's done, make sure the graph datapoints are in order intervals = sorted(list(graphs[list(graphs)[0]].keys())) # delete graphs that do not have the required amount of intervals # this is useful to get rid of outliers and items that only occur # very few times over the full interval if completeness > 0: intervals_required = len(intervals) * (completeness / 100) disqualified = [] for graph in graphs: if len(intervals) - missing[graph] < intervals_required: disqualified.append(graph) graphs = { graph: graphs[graph] for graph in graphs if graph not in disqualified } # determine max value per item, so we can normalize them later limits = {} max_limit = 0 for graph in graphs: for interval in graphs[graph]: limits[graph] = max(limits.get(graph, 0), abs(graphs[graph][interval])) max_limit = max(max_limit, abs(graphs[graph][interval])) # order graphs by highest (or lowest) value) limits = { limit: limits[limit] for limit in sorted(limits, key=lambda l: limits[l]) } graphs = {graph: graphs[graph] for graph in limits} if not graphs: # maybe nothing is actually there to be graphed self.dataset.update_status( "No items match the selection criteria - nothing to visualise." ) self.dataset.finish(0) return None # how many vertical grid lines (and labels) are to be included at most # 12 is a sensible default because it allows one label per month for a full # year's data max_gridlines = 12 # If True, label is put at the lower left bottom of the graph rather than # outside it. Automatically set to True if one of the labels is long, as # else the label would fall off the screen label_in_graph = max([len(item) for item in graphs]) > 30 # determine how wide each interval should be # the graph has a minimum width - but the graph's width will be # extended if at this minimum width each item does not have the # minimum per-item width min_full_width = 600 min_item_width = 50 item_width = max(min_item_width, min_full_width / len(intervals)) # determine how much space each graph should get # same trade-off as for the interval width min_full_height = 300 min_item_height = 100 item_height = max(min_item_height, min_full_height / len(graphs)) # margin - this should be enough for the text labels to fit in margin_base = 50 margin_right = margin_base * 4 margin_top = margin_base * 3 # this determines the "flatness" of the isometric projection and an be # tweaked for different looks - basically corresponds to how far the # camera is above the horizon plane_angle = 120 # don't change these plane_obverse = radians((180 - plane_angle) / 2) plane_angle = radians(plane_angle) # okay, now determine the full graphic size with these dimensions projected # semi-isometrically. We can also use these values later for drawing for # drawing grid lines, et cetera. The axis widths and heights here are the # dimensions of the bounding box wrapping the isometrically projected axes. x_axis_length = (item_width * (len(intervals) - 1)) y_axis_length = (item_height * len(graphs)) x_axis_width = (sin(plane_angle / 2) * x_axis_length) y_axis_width = (sin(plane_angle / 2) * y_axis_length) canvas_width = x_axis_width + y_axis_width # leave room for graph header if graph_label: margin_top += (2 * (canvas_width / 50)) x_axis_height = (cos(plane_angle / 2) * x_axis_length) y_axis_height = (cos(plane_angle / 2) * y_axis_length) canvas_height = x_axis_height + y_axis_height # now we have the dimensions, the canvas can be instantiated canvas = get_4cat_canvas( self.dataset.get_results_path(), width=(canvas_width + margin_base + margin_right), height=(canvas_height + margin_base + margin_top), header=graph_label) # draw gridlines - vertical gridline_x = y_axis_width + margin_base gridline_y = margin_top + canvas_height step_x_horizontal = sin(plane_angle / 2) * item_width step_y_horizontal = cos(plane_angle / 2) * item_width step_x_vertical = sin(plane_angle / 2) * item_height step_y_vertical = cos(plane_angle / 2) * item_height # labels for x axis # month and week both follow the same pattern # it's not always possible to distinguish between them but we will try # by looking for months greater than 12 in which case we are dealing # with weeks # we need to know this because for months there is an extra row in the # label with the full month is_week = False for i in range(0, len(intervals)): if re.match(r"^[0-9]{4}-[0-9]{2}", intervals[i]) and int(intervals[i].split("-")[1]) > 12: is_week = True break skip = max(1, int(len(intervals) / max_gridlines)) for i in range(0, len(intervals)): if i % skip == 0: canvas.add( Line(start=(gridline_x, gridline_y), end=(gridline_x - y_axis_width, gridline_y - y_axis_height), stroke="grey", stroke_width=0.25)) # to properly position the rotated and skewed text a container # element is needed label1 = str(intervals[i])[0:4] center = (gridline_x, gridline_y) container = SVG(x=center[0] - 25, y=center[1], width="50", height="1.5em", overflow="visible", style="font-size:0.8em;") container.add( Text(insert=("25%", "100%"), text=label1, transform="rotate(%f) skewX(%f)" % (-degrees(plane_obverse), degrees(plane_obverse)), text_anchor="middle", baseline_shift="-0.5em", style="font-weight:bold;")) if re.match(r"^[0-9]{4}-[0-9]{2}", intervals[i]) and not is_week: label2 = month_abbr[int(str(intervals[i])[5:7])] if re.match(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}", intervals[i]): label2 += " %i" % int(intervals[i][8:10]) container.add( Text(insert=("25%", "150%"), text=label2, transform="rotate(%f) skewX(%f)" % (-degrees(plane_obverse), degrees(plane_obverse)), text_anchor="middle", baseline_shift="-0.5em")) canvas.add(container) gridline_x += step_x_horizontal gridline_y -= step_y_horizontal # draw graphs as filled beziers top = step_y_vertical * 1.5 graph_start_x = y_axis_width + margin_base graph_start_y = margin_top + canvas_height # draw graphs in reverse order, so the bottom one is most in the # foreground (in case of overlap) for graph in reversed(list(graphs)): self.dataset.update_status("Rendering graph for '%s'" % graph) # path starting at lower left corner of graph area_graph = Path(fill=self.colours[self.colour_index]) area_graph.push("M %f %f" % (graph_start_x, graph_start_y)) previous_value = None graph_x = graph_start_x graph_y = graph_start_y for interval in graphs[graph]: # normalise value value = graphs[graph][interval] try: limit = limits[graph] if normalise_values else max_limit value = top * copysign(abs(value) / limit, value) except ZeroDivisionError: value = 0 if previous_value is None: # vertical line upwards to starting value of graph area_graph.push("L %f %f" % (graph_start_x, graph_start_y - value)) elif not smooth: area_graph.push("L %f %f" % (graph_x, graph_y - value)) else: # quadratic bezier from previous value to current value control_left = (graph_x - (step_x_horizontal / 2), graph_y + step_y_horizontal - previous_value - (step_y_horizontal / 2)) control_right = (graph_x - (step_x_horizontal / 2), graph_y - value + (step_y_horizontal / 2)) area_graph.push("C %f %f %f %f %f %f" % (*control_left, *control_right, graph_x, graph_y - value)) previous_value = value graph_x += step_x_horizontal graph_y -= step_y_horizontal # line to the bottom of the graph at the current Y position area_graph.push( "L %f %f" % (graph_x - step_x_horizontal, graph_y + step_y_horizontal)) area_graph.push("Z") # then close the Path canvas.add(area_graph) # add text labels - skewing is a bit complicated and we need a # "center" to translate the origins properly. if label_in_graph: insert = (graph_start_x + 5, graph_start_y - 10) else: insert = (graph_x - (step_x_horizontal) + 5, graph_y + step_y_horizontal - 10) # we need to take the skewing into account for the translation offset_y = tan(plane_obverse) * insert[0] canvas.add( Text(insert=(0, 0), text=graph, transform="skewY(%f) translate(%f %f)" % (-degrees(plane_obverse), insert[0], insert[1] + offset_y))) # cycle colours, back to the beginning if all have been used self.colour_index += 1 if self.colour_index >= len(self.colours): self.colour_index = 0 graph_start_x -= step_x_vertical graph_start_y -= step_y_vertical # draw gridlines - horizontal gridline_x = margin_base gridline_y = margin_top + canvas_height - y_axis_height for graph in graphs: gridline_x += step_x_vertical gridline_y += step_y_vertical canvas.add( Line(start=(gridline_x, gridline_y), end=(gridline_x + x_axis_width, gridline_y - x_axis_height), stroke="black", stroke_width=1)) # x axis canvas.add( Line(start=(margin_base + y_axis_width, margin_top + canvas_height), end=(margin_base + canvas_width, margin_top + canvas_height - x_axis_height), stroke="black", stroke_width=2)) # and finally save the SVG canvas.save(pretty=True) self.dataset.finish(len(graphs))
def test_push_arc_2(self): p = Path('m0,0') p.push_arc(target=(7,7), rotation=30, r=(2,4), large_arc=False, angle_dir='-', absolute=True) self.assertEqual(p.tostring(), '<path d="m0,0 A 2 4 30 0,0 7 7" />')
def test_push_arc_1(self): p = Path('m0,0') p.push_arc(target=(7,7), rotation=30, r=5) self.assertEqual(p.tostring(), '<path d="m0,0 a 5 5 30 1,1 7 7" />')
def test_nested_commands(self): p = Path(d=('M 1,2', ['L', (7, 7, 'H 1 2 3 4 5')])) self.assertEqual(p.tostring(), '<path d="M 1,2 L 7 7 H 1 2 3 4 5" />')
def test_flat_commands(self): p = Path(d="M 0 0") self.assertEqual(p.tostring(), '<path d="M 0 0" />') # push separated commands and values p.push(100, 100, 100, 200) self.assertEqual(p.tostring(), '<path d="M 0 0 100 100 100 200" />') # push commands strings p = Path() p.push('M 100 100 100 200') self.assertEqual(p.tostring(), '<path d="M 100 100 100 200" />') p = Path(d=('M 10', 7)) p.push('l', 100., 100.) p.push('v 100.7 200.1') self.assertEqual(p.tostring(), '<path d="M 10 7 l 100.0 100.0 v 100.7 200.1" />')
def draw(self, svg_file, max_lines=12): if self.intensity < self.filter_ratio: return path = Path(("M", self.left, self.top), stroke="black", stroke_width="0.3", fill="none") count = int((max_lines * self.intensity)) left_third = self.left + (self.block_size / 4.0) right_third = self.left + (3 * (self.block_size / 4.0)) top_third = self.top + (self.block_size / 4.0) bottom_third = self.top + (3 * (self.block_size / 4.0)) for m in range(count): if m == 0: path.push("L", self.right, self.bottom) path.push("M", self.right, self.top) elif m == 1: path.push("L", self.left, self.bottom) path.push("M", self.mid_x, self.top) elif m == 2: path.push("L", self.mid_x, self.bottom) path.push("M", self.left, self.mid_y) elif m == 3: path.push("L", self.right, self.mid_y) path.push("M", left_third, self.top) elif m == 4: path.push("L", left_third, self.bottom) path.push("M", right_third, self.top) elif m == 5: path.push("L", right_third, self.bottom) path.push("M", self.left, top_third) elif m == 6: path.push("L", self.right, top_third) path.push("M", self.left, bottom_third) elif m == 7: path.push("L", self.right, bottom_third) path.push("M", left_third, self.top) elif m == 8: path.push("L", self.right, bottom_third) path.push("M", right_third, self.top) elif m == 9: path.push("L", self.left, bottom_third) path.push("M", left_third, self.bottom) elif m == 10: path.push("L", self.right, top_third) path.push("M", right_third, self.bottom) elif m == 11: path.push("L", self.left, top_third) svg_file.add(path)
def semicircle(s, r, direction, color='orange'): return Path('M {} {} A {} {} 0 1 {} {} {}'.format(*s, r, r, direction, s[0], s[1] - 2 * r), stroke=color, stroke_width='0.05', fill='none')
def get_path(self, points, translate_x, style={}): parts = [ 'M {}{}'.format(round(start_x + translate_x, 7), rest) for start_x, rest in points ] return Path(d=' '.join(parts), **style)
def get_svg(self, **kwargs): path = Path([str(self)], **kwargs) return path