class Gauge(object): config = { 'id' : None, 'title' : 'Title', 'titleFontColor' : '#999999', 'value' : 0, 'valueFontColor' : '#010101', 'min' : 0, 'max' : 100, 'showMinMax' : True, 'gaugeWidthScale' : 1.0, 'gaugeColor' : '#edebeb', 'label' : "", 'showInnerShadow' : True, 'shadowOpacity' : 0.2, 'shadowSize' : 5, 'shadowVerticalOffset' : 3, 'levelColors' : ["#a9d70b", "#f9c802", "#ff0000"], 'levelColorsGradient' : True, 'labelFontColor' : "#b3b3b3", 'showNeedle' : False, 'needleColor' : "#b3b3b3", 'canvasWidth' : 400, 'canvasHeight' : 300, } def __init__(self, *args, **kwargs): for param_name, param_value in kwargs.items(): if self.config.has_key(param_name): self.config[param_name] = param_value # Overflow values if self.config['value'] > self.config['max']: self.config['value'] = self.config['max'] if self.config['value'] < self.config['min']: self.config['value'] = self.config['min'] self.originalValue = self.config['value'] self.canvas = Drawing(size=(self.config['canvasWidth'], self.config['canvasHeight'])) canvasW = self.config['canvasWidth'] canvasH = self.config['canvasHeight'] self.canvas.add(self.canvas.rect(insert=(0, 0), size=(canvasW, canvasH), stroke="none", fill="#ffffff")) # widget dimensions widgetW, widgetH = None, None if ((canvasW / canvasH) > 1.25): widgetW = 1.25 * canvasH widgetH = canvasH else: widgetW = canvasW widgetH = canvasW / 1.25 # delta dx = (canvasW - widgetW)/2 dy = (canvasH - widgetH)/2 # title titleFontSize = ((widgetH / 8) > 10) and (widgetH / 10) or 10 titleX = dx + widgetW / 2 titleY = dy + widgetH / 6.5 # value valueFontSize = ((widgetH / 6.4) > 16) and (widgetH / 6.4) or 16 valueX = dx + widgetW / 2 valueY = dy + widgetH / 1.4 # label labelFontSize = ((widgetH / 16) > 10) and (widgetH / 16) or 10 labelX = dx + widgetW / 2 labelY = valueY + valueFontSize / 2 + 6 # min minFontSize = ((widgetH / 16) > 10) and (widgetH / 16) or 10 minX = dx + (widgetW / 10) + (widgetW / 6.666666666666667 * self.config['gaugeWidthScale']) / 2 minY = dy + widgetH / 1.126760563380282 # max maxFontSize = ((widgetH / 16) > 10) and (widgetH / 16) or 10 maxX = dx + widgetW - (widgetW / 10) - (widgetW / 6.666666666666667 * self.config['gaugeWidthScale']) / 2 maxY = dy + widgetH / 1.126760563380282 # parameters self.params = { 'canvasW' : canvasW, 'canvasH' : canvasH, 'widgetW' : widgetW, 'widgetH' : widgetH, 'dx' : dx, 'dy' : dy, 'titleFontSize' : titleFontSize, 'titleX' : titleX, 'titleY' : titleY, 'valueFontSize' : valueFontSize, 'valueX' : valueX, 'valueY' : valueY, 'labelFontSize' : labelFontSize, 'labelX' : labelX, 'labelY' : labelY, 'minFontSize' : minFontSize, 'minX' : minX, 'minY' : minY, 'maxFontSize' : maxFontSize, 'maxX' : maxX, 'maxY' : maxY } # gauge self.gauge = self.gauge_path(self.config['max'], self.config['min'], self.config['max'], self.params['widgetW'], self.params['widgetH'], self.params['dx'], self.params['dy'], self.config['gaugeWidthScale'], stroke='none', fill=self.config['gaugeColor']) self.canvas.add(self.gauge) # level percent_value = (self.config['value'] - self.config['min']) / (self.config['max'] - self.config['min']) self.level = self.gauge_path(self.config['value'], self.config['min'], self.config['max'], self.params['widgetW'], self.params['widgetH'], self.params['dx'], self.params['dy'], self.config['gaugeWidthScale'], stroke='none', fill=self.get_color_for_value(percent_value, self.config['levelColors'], self.config['levelColorsGradient'])) self.canvas.add(self.level) # needle if self.config['showNeedle']: self.needle = self.needle_path(self.config['value'], self.config['min'], self.config['max'], self.params['widgetW'], self.params['widgetH'], self.params['dx'], self.params['dy'], self.config['gaugeWidthScale'], stroke='none', fill=self.config['needleColor']) self.canvas.add(self.needle) # Value else: text_config = { "font-size" : "%d" % self.params['valueFontSize'], "font-weight" : "bold", "font-family" : "Arial", "fill" : self.config['valueFontColor'], "fill-opacity" : "1", "text-anchor" : 'middle' } value_text = self.canvas.text('', insert=('%d' % self.params['valueX'], '%d' % self.params['valueY']), **text_config) value_tspan = self.canvas.tspan(self.originalValue, dy=[8]) value_text.add(value_tspan) self.canvas.add(value_text) # Add min & max value self.show_minmax() def save(self, path): svg = self.canvas.tostring() svg2png = getattr(cairosvg, 'svg2png') png_byte = svg2png(bytestring=svg) f = open(path,'w') f.write(png_byte) f.close() def gauge_path(self, value, val_min, val_max, w, h, dx, dy, gws, **extra): alpha = (1 - (value - val_min) / (val_max - val_min)) * math.pi Ro = w / 2 - w / 10 Ri = Ro - w / 6.666666666666667 * gws Cx = w / 2 + dx Cy = h / 1.25 + dy Xo = w / 2 + dx + Ro * math.cos(alpha) Yo = h - (h - Cy) + dy - Ro * math.sin(alpha) Xi = w / 2 + dx + Ri * math.cos(alpha) Yi = h - (h - Cy) + dy - Ri * math.sin(alpha) path = [] path.append(u"M%d,%d " % ((Cx - Ri), Cy)) path.append(u"L%d,%d " % ((Cx - Ro), Cy)) path.append(u"A%d,%d 0 0,1 %d,%d " % (Ro, Ro, Xo, Yo)) path.append(u"L%d,%d " % (Xi, Yi)) path.append(u"A%d,%d 0 0,0 %d,%d " % (Ri, Ri, (Cx - Ri), Cy)) path.append(u"z ") return Path(d=path, **extra) def needle_path(self, value, val_min, val_max, w, h, dx, dy, gws, **extra): xO = w / 2 + dx yO = h / 1.25 + dy Rext = w / 2 - w / 10 Rint = Rext - w / 6.666666666666667 * gws x_offset = xO y_offset = h - (h - yO) + dy val = (value - val_min) / (val_max - val_min) angle_b = val<0.5 and val*math.pi or (math.pi - val*math.pi) # Angle de la pointe angle_a = math.pi/2 - angle_b angle_c = math.pi/2 - angle_b rayon_base = 7 rayon_b = Rint + (Rext-Rint)*10/100 xA = x_offset + -1 * rayon_base * math.cos(angle_a) yA = y_offset - (val<0.5 and -1 or 1) * rayon_base * math.sin(angle_a) xC = x_offset + 1 * rayon_base * math.cos(angle_c) yC = y_offset - (val<0.5 and 1 or -1) * rayon_base * math.sin(angle_c) xB = x_offset + (val<0.5 and -1 or 1) * rayon_b * math.cos(angle_b) yB = y_offset - rayon_b * math.sin(angle_b) path = [] path.append(u"M%d,%d " % (xA, yA)) path.append(u"L%d,%d " % (xB, yB)) path.append(u"L%d,%d " % (xC, yC)) path.append(u"A%d,%d 0 1,1 %d,%d " % (rayon_base, rayon_base, xA, yA)) path.append(u"z ") return Path(d=path, **extra) def get_color_for_value(self, pct, color, grad): no = len(color); if no == 1: return color[0] HEX = r'[a-fA-F\d]{2}' HEX_COLOR = r'#(?P<red>%(hex)s)(?P<green>%(hex)s)(?P<blue>%(hex)s)' % {'hex': HEX} inc = grad and (1 / (no - 1)) or (1 / no) colors = [] i = 0 while i < no: percentage = (grad) and (inc * i) or (inc * (i + 1)) parts = re.match(HEX_COLOR,color[i]).groupdict() rval = int(parts['red'], 16) gval = int(parts['green'], 16) bval = int(parts['blue'], 16) colors.append({ 'pct': percentage, 'color': { 'r': rval, 'g': gval, 'b': bval } }) i+=1 if pct == 0: return 'rgb(%d,%d,%d)' % (colors[0]['color']['r'], colors[0]['color']['g'], colors[0]['color']['b']) i = 0 while i < len(colors): if pct <= colors[i]['pct']: if (grad == True): lower = colors[i-1] upper = colors[i] _range = upper['pct'] - lower['pct'] rangePct = (pct - lower['pct']) / _range pctLower = 1 - rangePct pctUpper = rangePct color = { 'r': math.floor(lower['color']['r'] * pctLower + upper['color']['r'] * pctUpper), 'g': math.floor(lower['color']['g'] * pctLower + upper['color']['g'] * pctUpper), 'b': math.floor(lower['color']['b'] * pctLower + upper['color']['b'] * pctUpper) } return 'rgb(%d,%d,%d)' % (color['r'], color['g'], color['b']) else: return 'rgb(%d,%d,%d)' % (colors[i]['color']['r'], colors[i]['color']['g'], colors[i]['color']['b']) i+=1 def show_minmax(self): # min txtMin_config = { "font-size" : '%d' % self.params['minFontSize'], "font-weight" : "normal", "font-family" : "Arial", "fill" : self.config['labelFontColor'], "fill-opacity" : self.config['showMinMax'] and "1" or "0", "text-anchor" : 'middle' } txtMin = self.canvas.text(self.config['min'], insert=(self.params['minX'], self.params['minY']), **txtMin_config) self.canvas.add(txtMin) # max txtMax_config = { "font-size" : '%d' % self.params['maxFontSize'], "font-weight" :"normal", "font-family" :"Arial", "fill" : self.config['labelFontColor'], "fill-opacity" : self.config['showMinMax'] and "1" or "0", "text-anchor" : 'middle' } txtMax = self.canvas.text(self.config['max'], insert=(self.params['maxX'], self.params['maxY']), **txtMax_config) self.canvas.add(txtMax)
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 create_empty_chart(full_file_name): """Creates a chart of the proper dimensions with a white background.""" num_days_in_week = 7 num_weeks = math.ceil(NUM_DAYS_TO_SHOW / 7.0) if date.today().weekday() + 1 < NUM_DAYS_TO_SHOW % 7: # We need to draw NUM_DAYS_TO_SHOW % 7 extra days, but on the last week # we have only space for date.today().weekday() + 1 days. num_weeks += 1 width = 2 * MARGIN + num_weeks * DAY_BOX_SIZE + \ (num_weeks - 1) * DAY_BOX_SEPARATION height = 2 * MARGIN + num_days_in_week * DAY_BOX_SIZE + \ (num_days_in_week - 1) * DAY_BOX_SEPARATION chart = Drawing(full_file_name, size=(width, height)) chart.add(chart.rect(insert=(0, 0), size=(width, height), fill='white')) return chart
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()
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_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 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(self, dr: svgwrite.Drawing, size: XY, offset: XY): """Draw the heatmap based on tracks.""" normal_lines = [] special_lines = [] bbox = self._determine_bbox() for tr in self.poster.tracks: for line in utils.project(bbox, size, offset, tr.polylines): if tr.special: special_lines.append(line) else: normal_lines.append(line) for lines, color in [(normal_lines, self.poster.colors['track']), (special_lines, self.poster.colors['special'])]: for opacity, width in [(0.1, 5.0), (0.2, 2.0), (1.0, 0.3)]: for line in lines: dr.add( dr.polyline(points=line, stroke=color, stroke_opacity=opacity, fill='none', stroke_width=width, stroke_linejoin='round', stroke_linecap='round'))
def test_draw_template(self): # def draw_template(self, canvas, template, target_width, height, labels=None, colors=None): d = DiagramSettings() canvas = Drawing(size=(1000, 50)) t = genomic.Template('1', 1, 100000, bands=[ BioInterval(None, 1, 8000, 'p1'), BioInterval(None, 10000, 15000, 'p2') ]) g = draw_template(d, canvas, t, 1000) canvas.add(g) canvas.attribs['height'] = g.height canvas = Drawing(size=(1000, 50)) g = draw_template(d, canvas, TEMPLATE_METADATA['1'], 1000) self.assertEqual( d.breakpoint_top_margin + d.breakpoint_bottom_margin + d.template_track_height, g.height) canvas.add(g) canvas.attribs['height'] = g.height self.assertEqual(2, len(canvas.elements))
def create_svg_document(elements, size, viewbox=None, background_color="white", background_opacity=1.0): """Create the full SVG document. :param viewbox: (minx, miny, width, height) """ dwg = Drawing("ase.svg", profile="tiny", size=size) root = Group(id="root") dwg.add(root) # if Color(background_color).web != "white": # apparently the best way, see: https://stackoverflow.com/a/11293812/5033292 root.add( shapes.Rect(size=size, fill=background_color, fill_opacity=background_opacity)) for element in elements: root.add(element) if viewbox: dwg.viewbox(*viewbox) return dwg
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 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 add(self, svg: Drawing) -> None: point: np.array = to_grid(self.point) radius: float = 7.5 a1 = self.angle + math.pi / 9.0 a2 = self.angle - math.pi / 9.0 n1 = np.array((math.cos(a1), math.sin(a1))) n2 = np.array((math.cos(a2), math.sin(a2))) p1 = point + n1 * radius p2 = point + n1 * 20 p3 = point + n2 * 20 p4 = point + n2 * radius svg.add( svg.path(d=["M", p1, "C", p2, p3, p4], fill="none", stroke="black", stroke_width=0.5)) n = (p4 - p3) / np.linalg.norm(p4 - p3) svg.add( svg.path(d=create_v(p4, n, np.dot(rotation_matrix(-math.pi / 2.0), n)), stroke_width=0.5, fill="none", stroke="black"))
def _draw_year(self, d: svgwrite.Drawing, size: XY, offset: XY, year: int): min_size = min(size.x, size.y) outer_radius = 0.5 * min_size - 6 radius_range = ValueRange.from_pair(outer_radius / 4, outer_radius) center = offset + 0.5 * size if self._rings: self._draw_rings(d, center, radius_range) year_style = 'dominant-baseline: central; font-size:{}px; font-family:Arial;'.format(min_size * 4.0 / 80.0) month_style = 'font-size:{}px; font-family:Arial;'.format(min_size * 3.0 / 80.0) d.add(d.text('{}'.format(year), insert=center.tuple(), fill=self.poster.colors['text'], text_anchor="middle", alignment_baseline="middle", style=year_style)) df = 360.0 / (366 if calendar.isleap(year) else 365) day = 0 date = datetime.date(year, 1, 1) while date.year == year: text_date = date.strftime("%Y-%m-%d") a1 = math.radians(day * df) a2 = math.radians((day + 1) * df) if date.day == 1: (_, last_day) = calendar.monthrange(date.year, date.month) a3 = math.radians((day + last_day - 1) * df) sin_a1, cos_a1 = math.sin(a1), math.cos(a1) sin_a3, cos_a3 = math.sin(a3), math.cos(a3) r1 = outer_radius + 1 r2 = outer_radius + 6 r3 = outer_radius + 2 d.add(d.line( start=(center + r1 * XY(sin_a1, -cos_a1)).tuple(), end=(center + r2 * XY(sin_a1, -cos_a1)).tuple(), stroke=self.poster.colors['text'], stroke_width=0.3)) path = d.path(d=('M', center.x + r3 * sin_a1, center.y - r3 * cos_a1), fill='none', stroke='none') path.push('a{},{} 0 0,1 {},{}'.format(r3, r3, r3 * (sin_a3 - sin_a1), r3 * (cos_a1 - cos_a3))) d.add(path) tpath = svgwrite.text.TextPath(path, date.strftime("%B"), startOffset=(0.5 * r3 * (a3 - a1))) text = d.text("", fill=self.poster.colors['text'], text_anchor="middle", style=month_style) text.add(tpath) d.add(text) if text_date in self.poster.tracks_by_date: self._draw_circle_segment(d, self.poster.tracks_by_date[text_date], a1, a2, radius_range, center) day += 1 date += datetime.timedelta(1)
def _draw_classical_double_line(drawing: Drawing, x1_coord, y1_coord, x2_coord, y2_coord) -> None: """Draw a double line between (x1_coord,y1_coord) and (x2_coord,y2_coord). Raises: NotImplementedError: when x1_coord!=x2_coord and y1_coord!=y2_coord (i.e. when the line is neither horizontal nor vertical). """ x_increment, y_increment = 0, 0 if x1_coord == x2_coord: x_increment = _constants.DOUBLE_LINES_SEPARATION elif y1_coord == y2_coord: y_increment = _constants.DOUBLE_LINES_SEPARATION else: raise NotImplementedError("The drawed line should be either horizontal or vertical.") drawing.add(drawing.line(start=(x1_coord - x_increment, y1_coord - y_increment), end=(x2_coord - x_increment, y2_coord - y_increment), stroke=_constants.GATE_BORDER_COLOR, stroke_width=_constants.STROKE_THICKNESS)) drawing.add(drawing.line(start=(x1_coord + x_increment, y1_coord + y_increment), end=(x2_coord + x_increment, y2_coord + y_increment), stroke=_constants.GATE_BORDER_COLOR, stroke_width=_constants.STROKE_THICKNESS))
def draw(self, svg: svgwrite.Drawing, point: np.array, color: Color, opacity=1.0, tags: Dict[str, Any] = None, outline: bool = False): """ Draw icon shape into SVG file. :param svg: output SVG file :param point: icon position :param color: fill color :param opacity: icon opacity :param tags: tags to be displayed as hint :param outline: draw outline for the icon """ point = np.array(list(map(int, point))) path: svgwrite.path.Path = self.get_path(svg, point) path.update({"fill": color.hex}) if outline: opacity: float = 0.5 path.update({ "fill": color.hex, "stroke": color.hex, "stroke-width": 2.2, "stroke-linejoin": "round" }) if opacity != 1.0: path.update({"opacity": opacity}) if tags: title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags)) path.set_desc(title=title) svg.add(path)
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 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 create_svg_image(styles, board_size, hexagons): """ Creates SVG drawing. The drawing contains all given css styles, a board (background rectangle) of given size and all given hexagons. The board can be styled using '.board'. All hexagonal fields can be styled using '.hex-field'. Fields can be also styled using 'hex-field-X', where X is the type of the field. :param styles iterable of css styles (strings) :param board_size tuple representing board size (width, height) :param hexagons iterable of hexagons (tuples in a form of (vertices, type) ) :returns SVG Drawing object """ svg_image = Drawing() for style in styles: svg_image.add(svg_image.style(style)) svg_image.add(svg_image.rect(size=board_size, class_="board")) for hexagon in hexagons: svg_image.add(svg_image.polygon(hexagon.vertices, class_="hex-field hex-field-%d" % hexagon.type)) return svg_image
def draw(self, dr: svgwrite.Drawing, size: XY, offset: XY): if self.poster.tracks is None: raise PosterError("No tracks to draw") year_size = 200 * 4.0 / 80.0 year_style = f"font-size:{year_size}px; font-family:Arial;" year_length_style = f"font-size:{110 * 3.0 / 80.0}px; font-family:Arial;" month_names_style = f"font-size:2.5px; font-family:Arial" total_length_year_dict = self.poster.total_length_year_dict for year in range(self.poster.years.from_year, self.poster.years.to_year + 1): start_date_weekday, _ = calendar.monthrange(year, 1) github_rect_first_day = datetime.date(year, 1, 1) # Github profile the first day start from the last Monday of the last year or the first Monday of this year # It depands on if the first day of this year is Monday or not. github_rect_day = github_rect_first_day + datetime.timedelta( -start_date_weekday) year_length = total_length_year_dict.get(year, 0) year_length = format_float(self.poster.m2u(year_length)) try: month_names = [ locale.nl_langinfo(day)[:3] # Get only first three letters for day in [ locale.MON_1, locale.MON_2, locale.MON_3, locale.MON_4, locale.MON_5, locale.MON_6, locale.MON_7, locale.MON_8, locale.MON_9, locale.MON_10, locale.MON_11, locale.MON_12, ] ] # support windows or others doesn't support locale Name, by Hard code except Exception as e: print(str(e)) month_names = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ] km_or_mi = "mi" if self.poster.units == "metric": km_or_mi = "km" dr.add( dr.text( f"{year}", insert=offset.tuple(), fill=self.poster.colors["text"], alignment_baseline="hanging", style=year_style, )) dr.add( dr.text( f"{year_length} {km_or_mi}", insert=(offset.tuple()[0] + 165, offset.tuple()[1] + 2), fill=self.poster.colors["text"], alignment_baseline="hanging", style=year_length_style, )) # add month name up to the poster one by one because of svg text auto trim the spaces. for num, name in enumerate(month_names): dr.add( dr.text( f"{name}", insert=(offset.tuple()[0] + 15.5 * num, offset.tuple()[1] + 14), fill=self.poster.colors["text"], style=month_names_style, )) rect_x = 10.0 dom = (2.6, 2.6) # add every day of this year for 53 weeks and per week has 7 days for i in range(54): rect_y = offset.y + year_size + 2 for j in range(7): if int(github_rect_day.year) > year: break rect_y += 3.5 color = "#444444" date_title = str(github_rect_day) if date_title in self.poster.tracks_by_date: tracks = self.poster.tracks_by_date[date_title] length = sum([t.length for t in tracks]) distance1 = self.poster.special_distance[ "special_distance"] distance2 = self.poster.special_distance[ "special_distance2"] has_special = distance1 < length / 1000 < distance2 color = self.color(self.poster.length_range_by_date, length, has_special) if length / 1000 >= distance2: color = self.poster.colors.get( "special2") or self.poster.colors.get( "special") str_length = format_float(self.poster.m2u(length)) date_title = f"{date_title} {str_length} {km_or_mi}" rect = dr.rect((rect_x, rect_y), dom, fill=color) rect.set_desc(title=date_title) dr.add(rect) github_rect_day += datetime.timedelta(1) rect_x += 3.5 offset.y += 3.5 * 9 + year_size + 1.5
def _draw_footer(self, d: svgwrite.Drawing) -> None: g = d.g(id="footer") d.add(g) text_color = self.colors["text"] header_style = "font-size:4px; font-family:Arial" value_style = "font-size:9px; font-family:Arial" small_value_style = "font-size:3px; font-family:Arial" ( total_length, average_length, length_range, weeks, ) = self._compute_track_statistics() g.add( d.text( self.translate("ATHLETE"), insert=(10, self.height - 20), fill=text_color, style=header_style, ) ) g.add( d.text( self._athlete, insert=(10, self.height - 10), fill=text_color, style=value_style, ) ) g.add( d.text( self.translate("STATISTICS"), insert=(120, self.height - 20), fill=text_color, style=header_style, ) ) g.add( d.text( self.translate("Number") + f": {len(self.tracks)}", insert=(120, self.height - 15), fill=text_color, style=small_value_style, ) ) g.add( d.text( self.translate("Weekly") + ": " + format_float(len(self.tracks) / weeks), insert=(120, self.height - 10), fill=text_color, style=small_value_style, ) ) g.add( d.text( self.translate("Total") + ": " + self.format_distance(total_length), insert=(141, self.height - 15), fill=text_color, style=small_value_style, ) ) g.add( d.text( self.translate("Avg") + ": " + self.format_distance(average_length), insert=(141, self.height - 10), fill=text_color, style=small_value_style, ) ) if length_range.is_valid(): min_length = length_range.lower() max_length = length_range.upper() assert min_length is not None assert max_length is not None else: min_length = 0.0 max_length = 0.0 g.add( d.text( self.translate("Min") + ": " + self.format_distance(min_length), insert=(167, self.height - 15), fill=text_color, style=small_value_style, ) ) g.add( d.text( self.translate("Max") + ": " + self.format_distance(max_length), insert=(167, self.height - 10), fill=text_color, style=small_value_style, ) )
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)
def export_node(node, store, size=None): """Construct a SVG description for a workflow node. Args: node (NodeDef) store (dict of uid, def): elements definitions size (int, int): size of drawing in pixels Returns: (str) - SVG description of workflow node """ pfs = port_font_size # node size pr = port_radius pspace = pr * 9 nw = compute_node_width(node, node['name'], pspace) nh = label_font_size + 2 * pr + 2 * pfs + 2 + (2 * node_padding) # draw if size is None: size = (600, 600) paper = Drawing("workflow_node.svg", size, id="repr") lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="in_port") lg.add_stop_color(0, color='#3333ff') lg.add_stop_color(1, color='#2222ff') paper.defs.add(lg) lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="out_port") lg.add_stop_color(0, color='#ffff33') lg.add_stop_color(1, color='#9a9a00') paper.defs.add(lg) # body g = paper.add(paper.g()) # background lg = paper.linearGradient((0.5, 0), (0.5, 1.)) lg.add_stop_color(0, color='#8c8cff') lg.add_stop_color(1, color='#c8c8c8') paper.defs.add(lg) bg = paper.rect((-nw / 2, -nh / 2), (nw, nh), rx=node_padding, ry=node_padding, stroke_width=1) bg.stroke('#808080') bg.fill(lg) g.add(bg) # label style = ('font-size: %dpx; font-family: %s; ' 'text-anchor: middle' % (label_font_size, label_font)) frag = paper.tspan(node['name'], dy=[label_font_size // 3]) label = paper.text("", style=style, fill='#000000') label.add(frag) g.add(label) # ports port_style = ('font-size: %dpx; ' % pfs + 'font-family: %s; ' % label_font) onstyle = port_style + 'text-anchor: end' instyle = port_style + 'text-anchor: start' istyle = port_style + 'text-anchor: middle' nb = len(node['inputs']) py = -nh / 2 for i, pdef in enumerate(node['inputs']): px = i * pspace - pspace * (nb - 1) / 2 pg = g.add(paper.g()) pg.translate(px, py) idef = store.get(pdef['interface'], None) if idef is not None and 'url' in idef: link = pg.add(paper.a(href=idef['url'], target='_top')) else: link = pg port = paper.circle((0, 0), pr, stroke='#000000', stroke_width=1) port.fill("url(#in_port)") link.add(port) # port name frag = paper.tspan(pdef['name'], dy=[-2 * pr]) label = paper.text("", style=instyle, fill='#000000') label.rotate(-45) label.add(frag) pg.add(label) # port interface if idef is None: itxt = pdef['interface'] else: itxt = idef['name'] if len(itxt) > 10: itxt = itxt[:7] + "..." frag = paper.tspan(itxt, dy=[pr + pfs]) label = paper.text("", style=istyle, fill='#000000') label.add(frag) link.add(label) nb = len(node['outputs']) py = nh / 2 for i, pdef in enumerate(node['outputs']): px = i * pspace - pspace * (nb - 1) / 2 pg = g.add(paper.g()) pg.translate(px, py) idef = store.get(pdef['interface'], None) if idef is not None and 'url' in idef: link = pg.add(paper.a(href=idef['url'], target='_top')) else: link = pg port = paper.circle((0, 0), pr, stroke='#000000', stroke_width=1) port.fill("url(#out_port)") link.add(port) # port name frag = paper.tspan(pdef['name'], dy=[2 * pr + pfs // 2]) label = paper.text("", style=onstyle, fill='#000000') label.rotate(-45) label.add(frag) pg.add(label) # port interface if idef is None: itxt = pdef['interface'] else: itxt = idef['name'] if len(itxt) > 10: itxt = itxt[:7] + "..." frag = paper.tspan(itxt, dy=[- pr - 2]) label = paper.text("", style=istyle, fill='#000000') label.add(frag) link.add(label) # reformat whole drawing to fit screen xmin = - nw / 2 - draw_padding / 10. xmax = + nw / 2 + draw_padding / 10. if len(node['inputs']) == 0: inames_extend = 0 else: inames = [(len(pdef['name']), pdef['name']) for pdef in node['inputs']] inames_extend = string_size(sorted(inames)[-1][1], pfs) * 0.7 + pfs ymin = - nh / 2 - pr - inames_extend - draw_padding / 10. if len(node['outputs']) == 0: onames_extend = 0 else: onames = [(len(pdef['name']), pdef['name']) for pdef in node['outputs']] onames_extend = string_size(sorted(onames)[-1][1], pfs) * 0.7 + pfs ymax = + nh / 2 + pr + onames_extend + draw_padding / 10. w = float(size[0]) h = float(size[1]) ratio = max((xmax - xmin) / w, (ymax - ymin) / h) xsize = ratio * w ysize = ratio * h bb = (xmin * xsize / (xmax - xmin), ymin * ysize / (ymax - ymin), xsize, ysize) paper.viewbox(*bb) return paper.tostring(), bb
class TestDraw(unittest.TestCase): def setUp(self): self.canvas = Drawing(height=100, width=1000) def test_generate_interval_mapping_outside_range_error(self): temp = [ Interval(48556470, 48556646), Interval(48573290, 48573665), Interval(48575056, 48575078), ] mapping = generate_interval_mapping( input_intervals=temp, target_width=431.39453125, ratio=20, min_width=14, buffer_length=None, end=None, start=None, min_inter_width=10, ) st = min([x.start for x in temp]) end = min([x.end for x in temp]) Interval.convert_pos(mapping, st) Interval.convert_pos(mapping, end) def test_generate_gene_mapping_err(self): # _generate_interval_mapping [genomic.IntergenicRegion(11:77361962_77361962+)] 1181.39453125 5 30 None 77356962 77366962) ir = genomic.IntergenicRegion('11', 5000, 5000, STRAND.POS) tgt_width = 1000 d = DiagramSettings() d.gene_min_buffer = 10 # (self, canvas, gene, width, height, fill, label='', reference_genome=None) draw_genes(d, self.canvas, [ir], tgt_width, []) # _generate_interval_mapping ['Interval(29684391, 29684391)', 'Interval(29663998, 29696515)'] 1181.39453125 5 60 None 29662998 29697515 # def generate_interval_mapping(cls, input_intervals, target_width, ratio, min_width, buffer_length=None, start=None, end=None, min_inter_width=None) itvls = [Interval(29684391, 29684391), Interval(29663998, 29696515)] generate_interval_mapping(itvls, 1181.39, 5, 60, None, 29662998, 29697515) def test_split_intervals_into_tracks(self): # ----======--------- # ------======-------- # -----=============== t = split_intervals_into_tracks([(1, 3), (3, 7), (2, 2), (4, 5), (3, 10)]) self.assertEqual(3, len(t)) self.assertEqual([(1, 3), (4, 5)], t[0]) self.assertEqual([(2, 2), (3, 7)], t[1]) self.assertEqual([(3, 10)], t[2]) def test_draw_genes(self): x = genomic.Gene('1', 1000, 2000, strand=STRAND.POS) y = genomic.Gene('1', 5000, 7000, strand=STRAND.NEG) z = genomic.Gene('1', 1500, 2500, strand=STRAND.POS) d = DiagramSettings() breakpoints = [Breakpoint('1', 1100, 1200, orient=ORIENT.RIGHT)] g = draw_genes( d, self.canvas, [x, y, z], 500, breakpoints, { x: d.gene1_color, y: d.gene2_color_selected, z: d.gene2_color }, ) # test the class structure self.assertEqual(6, len(g.elements)) self.assertEqual('scaffold', g.elements[0].attribs.get('class', '')) for i in range(1, 4): self.assertEqual('gene', g.elements[i].attribs.get('class', '')) self.assertEqual('mask', g.elements[4].attribs.get('class', '')) self.assertEqual('breakpoint', g.elements[5].attribs.get('class', '')) self.assertEqual( d.track_height * 2 + d.padding + d.breakpoint_bottom_margin + d.breakpoint_top_margin, g.height, ) self.canvas.add(g) self.assertEqual(len(g.labels), 4) self.assertEqual(x, g.labels['G1']) self.assertEqual(z, g.labels['G2']) self.assertEqual(y, g.labels['G3']) self.assertEqual(breakpoints[0], g.labels['B1']) def test_draw_ustranscript(self): d = DiagramSettings() # domains = [protein.Domain()] d1 = protein.Domain('first', [(55, 61), (71, 73)]) d2 = protein.Domain('second', [(10, 20), (30, 34)]) t = build_transcript( gene=None, cds_start=50, cds_end=249, exons=[(1, 99), (200, 299), (400, 499)], strand=STRAND.NEG, domains=[d2, d1], ) b = Breakpoint('1', 350, 410, orient=ORIENT.LEFT) g = draw_ustranscript(d, self.canvas, t, 500, colors={t.exons[1]: '#FFFF00'}, breakpoints=[b]) self.canvas.add(g) # self.canvas.saveas('test_draw_ustranscript.svg') self.assertEqual(2, len(self.canvas.elements)) self.assertEqual(3, len(g.elements)) for el, cls in zip(g.elements[0].elements, ['splicing', 'exon_track', 'protein']): self.assertEqual(cls, el.attribs.get('class', '')) for el, cls in zip(g.elements[0].elements[1].elements, ['scaffold', 'exon', 'exon', 'exon']): self.assertEqual(cls, el.attribs.get('class', '')) for el, cls in zip(g.elements[0].elements[2].elements, ['translation', 'domain', 'domain']): self.assertEqual(cls, el.attribs.get('class', '')) self.assertEqual( sum([ d.track_height, d.splice_height, 2 * d.padding, d.domain_track_height * 2, d.translation_track_height, d.padding, d.breakpoint_top_margin, d.breakpoint_bottom_margin, ]), g.height, ) self.assertEqual(d1.name, g.labels['D1']) self.assertEqual(d2.name, g.labels['D2']) def test_draw_consec_exons(self): d = DiagramSettings() # domains = [protein.Domain()] t = build_transcript( gene=None, cds_start=50, cds_end=249, exons=[(1, 99), (200, 299), (300, 350), (400, 499)], strand=STRAND.POS, domains=[], ) b = Breakpoint('1', 350, 410, orient=ORIENT.LEFT) g = draw_ustranscript(d, self.canvas, t, 500, colors={t.exons[1]: '#FFFF00'}, breakpoints=[b]) self.canvas.add(g) if OUTPUT_SVG: self.canvas.saveas('test_draw_consec_exons.svg') # self.canvas.saveas('test_draw_ustranscript.svg') self.assertEqual(2, len(self.canvas.elements)) self.assertEqual(3, len(g.elements)) # check that only 2 splicing marks were created self.assertEqual(2, len(g.elements[0].elements[0].elements)) # get the second exon ex2 = g.elements[0].elements[1].elements[2].elements[0] print(ex2) self.assertAlmostEqual(120.7783426339, ex2.attribs.get('width')) # get the third exon ex3 = g.elements[0].elements[1].elements[3].elements[0] print(ex3) self.assertAlmostEqual(96.52494419642852, ex3.attribs.get('width')) def test_dynamic_label_color(self): self.assertEqual(HEX_WHITE, dynamic_label_color(HEX_BLACK)) self.assertEqual(HEX_BLACK, dynamic_label_color(HEX_WHITE)) def test_draw_legend(self): d = DiagramSettings() swatches = [ ('#000000', 'black'), ('#FF0000', 'red'), ('#0000FF', 'blue'), ('#00FF00', 'green'), ('#FFFF00', 'yellow'), ] g = draw_legend(d, self.canvas, swatches) self.canvas.add(g) self.assertEqual('legend', g.attribs.get('class', '')) self.assertEqual( d.legend_swatch_size * len(swatches) + d.padding * (len(swatches) - 1 + 2), g.height) self.assertEqual(6, len(g.elements)) self.assertEqual( 6 * d.legend_font_size * d.font_width_height_ratio + d.padding * 3 + d.legend_swatch_size, g.width, ) def test_draw_layout_single_transcript(self): d = DiagramSettings() d1 = protein.Domain('first', [(55, 61), (71, 73)]) d2 = protein.Domain('second', [(10, 20), (30, 34)]) g1 = genomic.Gene('1', 150, 1000, strand=STRAND.POS) t = build_transcript(g1, [(200, 299), (400, 499), (700, 899)], 50, 249, [d2, d1]) b1 = Breakpoint('1', 350, orient=ORIENT.RIGHT) b2 = Breakpoint('1', 600, orient=ORIENT.LEFT) bpp = BreakpointPair(b1, b2, opposing_strands=False, untemplated_seq='') ann = variant.Annotation(bpp, transcript1=t, transcript2=t, event_type=SVTYPE.DUP, protocol=PROTOCOL.GENOME) ann.add_gene(genomic.Gene('1', 1500, 1950, strand=STRAND.POS)) reference_genome = {'1': MockObject(seq=MockString('A'))} ft = variant.FusionTranscript.build(ann, reference_genome) ann.fusion = ft canvas, legend = draw_sv_summary_diagram(d, ann) self.assertEqual(4, len(canvas.elements)) # defs counts as element expected_height = (d.top_margin + d.bottom_margin + d.track_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.inner_margin + d.track_height + d.splice_height + d.padding + d.translation_track_height + d.padding * 2 + d.domain_track_height * 2 + d.inner_margin + d.track_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.splice_height) if OUTPUT_SVG: canvas.saveas('test_draw_layout_single_transcript.svg') self.assertEqual(expected_height, canvas.attribs['height']) def test_draw_layout_single_genomic(self): d = DiagramSettings() d1 = protein.Domain('first', [(55, 61), (71, 73)]) d2 = protein.Domain('second', [(10, 20), (30, 34)]) g1 = genomic.Gene('1', 150, 1000, strand=STRAND.POS) g2 = genomic.Gene('1', 5000, 7500, strand=STRAND.POS) t1 = build_transcript( gene=g1, cds_start=50, cds_end=249, exons=[(200, 299), (400, 499), (700, 899)], domains=[d2, d1], ) t2 = build_transcript( gene=g2, cds_start=20, cds_end=500, exons=[(5100, 5299), (5800, 6199), (6500, 6549), (6700, 6799)], domains=[], ) b1 = Breakpoint('1', 350, orient=ORIENT.LEFT) b2 = Breakpoint('1', 6500, orient=ORIENT.RIGHT) bpp = BreakpointPair(b1, b2, opposing_strands=False, untemplated_seq='') ann = variant.Annotation(bpp, transcript1=t1, transcript2=t2, event_type=SVTYPE.DEL, protocol=PROTOCOL.GENOME) ann.add_gene(genomic.Gene('1', 1500, 1950, strand=STRAND.POS)) ann.add_gene(genomic.Gene('1', 3000, 3980, strand=STRAND.POS)) ann.add_gene(genomic.Gene('1', 3700, 4400, strand=STRAND.NEG)) reference_genome = {'1': MockObject(seq=MockString('A'))} ft = variant.FusionTranscript.build(ann, reference_genome) ann.fusion = ft self.assertEqual(t1.exons[0], ft.exon_mapping[ft.exons[0].position]) self.assertEqual(t2.exons[2], ft.exon_mapping[ft.exons[1].position]) self.assertEqual(t2.exons[3], ft.exon_mapping[ft.exons[2].position]) canvas, legend = draw_sv_summary_diagram(d, ann) self.assertEqual(5, len(canvas.elements)) # defs counts as element expected_height = (d.top_margin + d.bottom_margin + d.track_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.inner_margin + d.track_height + d.splice_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.padding + d.translation_track_height + d.padding * 2 + d.domain_track_height * 2 + d.inner_margin + d.track_height + d.splice_height) self.assertEqual(expected_height, canvas.attribs['height']) if OUTPUT_SVG: canvas.saveas('test_draw_layout_single_genomic.svg') def test_draw_layout_translocation(self): d = DiagramSettings() d1 = protein.Domain('first', [(55, 61), (71, 73)]) d2 = protein.Domain('second', [(10, 20), (30, 34)]) g1 = genomic.Gene('1', 150, 1000, strand=STRAND.POS) g2 = genomic.Gene('2', 5000, 7500, strand=STRAND.NEG) t1 = build_transcript( gene=g1, cds_start=50, cds_end=249, exons=[(200, 299), (400, 499), (700, 899)], domains=[d2, d1], ) t2 = build_transcript( gene=g2, cds_start=120, cds_end=700, exons=[(5100, 5299), (5800, 6199), (6500, 6549), (6700, 6799)], domains=[], ) b1 = Breakpoint('1', 350, orient=ORIENT.LEFT) b2 = Breakpoint('2', 6520, orient=ORIENT.LEFT) bpp = BreakpointPair(b1, b2, opposing_strands=True, untemplated_seq='') ann = variant.Annotation(bpp, transcript1=t1, transcript2=t2, event_type=SVTYPE.ITRANS, protocol=PROTOCOL.GENOME) # genes 1 ann.add_gene(genomic.Gene('1', 1500, 1950, strand=STRAND.POS)) ann.add_gene(genomic.Gene('1', 3000, 3980, strand=STRAND.POS)) ann.add_gene(genomic.Gene('1', 3700, 4400, strand=STRAND.NEG)) # genes 2 ann.add_gene(genomic.Gene('2', 1500, 1950, strand=STRAND.NEG)) ann.add_gene(genomic.Gene('2', 5500, 9000, strand=STRAND.POS)) ann.add_gene(genomic.Gene('2', 3700, 4400, strand=STRAND.NEG)) reference_genome = { '1': MockObject(seq=MockString('A')), '2': MockObject(seq=MockString('A')), } ft = variant.FusionTranscript.build(ann, reference_genome) ann.fusion = ft canvas, legend = draw_sv_summary_diagram(d, ann) self.assertEqual(6, len(canvas.elements)) # defs counts as element expected_height = ( d.top_margin + d.bottom_margin + d.track_height * 2 + d.padding + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.inner_margin + d.track_height + d.splice_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.padding + d.translation_track_height + d.padding * 2 + d.domain_track_height * 2 + d.inner_margin + d.track_height + d.splice_height) self.assertEqual(expected_height, canvas.attribs['height']) def test_draw_template(self): # def draw_template(self, canvas, template, target_width, height, labels=None, colors=None): d = DiagramSettings() canvas = Drawing(size=(1000, 50)) t = genomic.Template( '1', 1, 100000, bands=[ BioInterval(None, 1, 8000, 'p1'), BioInterval(None, 10000, 15000, 'p2') ], ) g = draw_template(d, canvas, t, 1000) canvas.add(g) canvas.attribs['height'] = g.height canvas = Drawing(size=(1000, 50)) g = draw_template(d, canvas, TEMPLATE_METADATA['1'], 1000) self.assertEqual( d.breakpoint_top_margin + d.breakpoint_bottom_margin + d.template_track_height, g.height) canvas.add(g) canvas.attribs['height'] = g.height self.assertEqual(2, len(canvas.elements)) def test_draw_translocation_with_template(self): d = DiagramSettings() d1 = protein.Domain('PF0001', [(55, 61), (71, 73)]) d2 = protein.Domain('PF0002', [(10, 20), (30, 34)]) g1 = genomic.Gene(TEMPLATE_METADATA['1'], 150, 1000, strand=STRAND.POS, aliases=['HUGO2']) g2 = genomic.Gene(TEMPLATE_METADATA['X'], 5000, 7500, strand=STRAND.NEG, aliases=['HUGO3']) t1 = build_transcript( gene=g1, name='transcript1', cds_start=50, cds_end=249, exons=[(200, 299), (400, 499), (700, 899)], domains=[d2, d1], ) t2 = build_transcript( gene=g2, name='transcript2', cds_start=120, cds_end=700, exons=[(5100, 5299), (5800, 6199), (6500, 6549), (6700, 6799)], domains=[], ) b1 = Breakpoint('1', 350, orient=ORIENT.LEFT) b2 = Breakpoint('2', 6520, orient=ORIENT.LEFT) bpp = BreakpointPair(b1, b2, opposing_strands=True, untemplated_seq='') ann = variant.Annotation(bpp, transcript1=t1, transcript2=t2, event_type=SVTYPE.ITRANS, protocol=PROTOCOL.GENOME) # genes 1 ann.add_gene( genomic.Gene('1', 1500, 1950, strand=STRAND.POS, aliases=['HUGO5'])) ann.add_gene(genomic.Gene('1', 3000, 3980, strand=STRAND.POS)) ann.add_gene(genomic.Gene('1', 3700, 4400, strand=STRAND.NEG)) # genes 2 ann.add_gene(genomic.Gene('2', 1500, 1950, strand=STRAND.NEG)) ann.add_gene(genomic.Gene('2', 5500, 9000, strand=STRAND.POS)) ann.add_gene(genomic.Gene('2', 3700, 4400, strand=STRAND.NEG)) reference_genome = { '1': MockObject(seq=MockString('A')), '2': MockObject(seq=MockString('A')), } ft = variant.FusionTranscript.build(ann, reference_genome) ann.fusion = ft canvas, legend = draw_sv_summary_diagram(d, ann, draw_reference_templates=True, templates=TEMPLATE_METADATA) if OUTPUT_SVG: canvas.saveas('test_draw_translocation_with_template.svg') self.assertEqual(8, len(canvas.elements)) # defs counts as element expected_height = ( d.top_margin + d.bottom_margin + d.track_height * 2 + d.padding + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.inner_margin + d.track_height + d.splice_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.padding + d.translation_track_height + d.padding * 2 + d.domain_track_height * 2 + d.inner_margin * 2 + d.track_height + d.breakpoint_bottom_margin + d.breakpoint_top_margin + d.splice_height + d.template_track_height) self.assertAlmostEqual(expected_height, canvas.attribs['height']) def test_draw_overlay(self): gene = genomic.Gene('12', 25357723, 25403870, strand=STRAND.NEG, name='KRAS') marker = BioInterval('12', 25403865, name='splice site mutation') t = build_transcript( cds_start=193, cds_end=759, exons=[ (25403685, 25403865), (25398208, 25398329), (25380168, 25380346), (25378548, 25378707), (25357723, 25362845), ], gene=gene, domains=[], ) build_transcript( cds_start=198, cds_end=425, exons=[(25403685, 25403870), (25398208, 25398329), (25362102, 25362845)], gene=gene, domains=[], ) build_transcript( cds_start=65, cds_end=634, exons=[ (25403685, 25403737), (25398208, 25398329), (25380168, 25380346), (25378548, 25378707), (25368371, 25368494), (25362365, 25362845), ], gene=gene, domains=[ protein.Domain('domain1', [(1, 10)]), protein.Domain('domain1', [(4, 10)]) ], is_best_transcript=True, ) build_transcript( cds_start=65, cds_end=634, exons=[(25403698, 25403863), (25398208, 25398329), (25386753, 25388160)], gene=gene, domains=[], ) d = DiagramSettings() for i, t in enumerate(gene.transcripts): t.name = 'transcript {}'.format(i + 1) scatterx = [x + 100 for x in range(gene.start, gene.end + 1, 400)] scattery = [random.uniform(-0.2, 0.2) for x in scatterx] s = ScatterPlot(list(zip(scatterx, scattery)), 'cna', ymin=-1, ymax=1, yticks=[-1, 0, 1]) d.gene_min_buffer = 0 canvas = draw_multi_transcript_overlay(d, gene, vmarkers=[marker], plots=[s, s]) self.assertEqual(2, len(canvas.elements)) # defs counts as element if OUTPUT_SVG: canvas.saveas('test_draw_overlay.svg') def test_single_bp_ins_exon(self): transcript = fusion.FusionTranscript() transcript.position = Interval(401258, 408265) transcript.exons = [ genomic.Exon(401258, 401461, transcript=transcript), genomic.Exon(404799, 405254, intact_end_splice=False, transcript=transcript), genomic.Exon( 405255, 405255, intact_start_splice=False, intact_end_splice=False, transcript=transcript, ), genomic.Exon(405256, 408265, intact_start_splice=False, transcript=transcript), ] cfg = DiagramSettings(width=1500) canvas = Drawing(size=(cfg.width, 1000)) drawing_width = cfg.width - cfg.label_left_margin - cfg.left_margin - cfg.right_margin canvas.add( draw_ustranscript(cfg, canvas, transcript, target_width=drawing_width)) if OUTPUT_SVG: canvas.saveas('test_single_bp_ins_exon.svg') def test_single_bp_dup(self): transcript = fusion.FusionTranscript() transcript.position = Interval(1, 500) transcript.exons = [ genomic.Exon(1, 7, transcript=transcript, intact_end_splice=False), genomic.Exon(8, 8, transcript=transcript, intact_start_splice=False, intact_end_splice=False), genomic.Exon(9, 100, transcript=transcript, intact_start_splice=False), genomic.Exon(200, 500, transcript=transcript), ] cfg = DiagramSettings(width=1500) canvas = Drawing(size=(cfg.width, 1000)) drawing_width = cfg.width - cfg.label_left_margin - cfg.left_margin - cfg.right_margin canvas.add( draw_ustranscript(cfg, canvas, transcript, target_width=drawing_width)) if OUTPUT_SVG: canvas.saveas('test_single_bp_dup.svg') def test_two_exon_transcript(self): transcript = fusion.FusionTranscript() transcript.position = Interval(1, 555) transcript.exons = [ genomic.Exon(55820038, 55820969, transcript=transcript), genomic.Exon(55820971, 55820976, transcript=transcript), ] transcript.exon_mapping[Interval(55820971, 55820976)] = MockObject( transcript=MockObject(exon_number=lambda *x: 2)) cfg = DiagramSettings(width=1500) canvas = Drawing(size=(cfg.width, 1000)) drawing_width = cfg.width - cfg.label_left_margin - cfg.left_margin - cfg.right_margin canvas.add( draw_ustranscript(cfg, canvas, transcript, target_width=drawing_width)) if OUTPUT_SVG: canvas.saveas('test_two_exon_transcript.svg')
r = corner_ruler(scale=scale, length=100, major_interval=10, minor_per_major=10) translate_inch(r, tool_size - diag_shift, diag_shift) utm_tool.add(r) # Small map ruler scale = 8000 diag_shift = 0.85 r = corner_ruler(scale=scale, length=200, major_interval=50, minor_per_major=5) translate_inch(r, tool_size - diag_shift, diag_shift) utm_tool.add(r) #------------------------------------------------------------------------------- # Repliacte onto printout #------------------------------------------------------------------------------- dwg = Drawing(size=(8.5 * inch, 11 * inch)) defs = Defs() defs.add(utm_tool) dwg.add(defs) for xi in range(3): for yi in range(4): dwg.add(UseInch( utm_tool, (0.5 + xi * 2.5, 0.5 + yi * 2.5), )) write_to_pdf(dwg, "UTM-roamer-ruler-VT.pdf")
# filter out the reverse path paths_to_add = [p for p in paths_to_add if p[0] != path_to_add[0]] start_location = paths[path_to_add[0]].start if path_to_add[0] else paths[ path_to_add[1]].end ''' if __name__ == "__main__": # make a graph of the next available grid num_grid = 8 spacing = 30 dwg = Drawing(join(OUTPUT_DIRECTORY, "next_available_grid.svg"), (num_grid * spacing, num_grid * spacing)) for x in range(num_grid): dwg.add( dwg.line(start=(0, x * spacing), end=(num_grid * spacing, x * spacing), stroke=rgb(10, 10, 16, '%'))) dwg.add( dwg.line(start=(x * spacing, 0), end=(x * spacing, num_grid * spacing), stroke=rgb(10, 10, 16, '%'))) start_point = [4, 4] dwg.add( dwg.rect(insert=(start_point[0] * spacing, start_point[1] * spacing), size=(spacing, spacing), fill=rgb(100, 100, 16, '%'))) count = 0 last_point = start_point for next_available in NextAvailableGrid(*start_point): dwg.add(
Figure built manually in SVG. Note, these require the svgwrite package (and optionally, the svglib package to convert to pdf). """ import subprocess from svgwrite import Drawing filename = 'scaling_compute_totals_gmres.svg' color_phys = '#85C1E9' color_scaled = '#EC7063' main_font_size = 20 dwg = Drawing(filename, (2500, 2000), debug=True) top_text = dwg.add(dwg.g(font_size=main_font_size, style="font-family: arial;")) locs = [ 'NL Inputs', 'NL Outputs', 'NL Residuals', 'LN Inputs', 'LN Outputs', 'LN Residuals', 'Jacobian' ] x = 650 y = 50 delta_x = 180 vertical_locs = [] for loc in locs: top_text.add(dwg.text(loc, (x - len(loc) * 4, y))) vertical_locs.append(x) x += delta_x
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 visualize(self, trajectory, output_file): drawing = Drawing() # Определение параметров образов состояний аппарата machine_view_length, machine_view_width = self.__machine_view_size coordinates_scaling = machine_view_length / self.__machine_length machine_diameter = \ ((machine_view_length * 2.0) ** 2.0 + machine_view_width ** 2.0) \ ** 0.5 machine_radius = machine_diameter / 2.0 # Создание последовательности записываемых состояний аппарата def generate_states_sequence(): spawn_time = 0.0 for trajectory_time, state in trajectory: if trajectory_time >= spawn_time: spawn_time += self.__time_interval yield state states_sequence = generate_states_sequence() # Запись последовательности состояний аппарата is_view_box_initialized = False view_box_minimal_x, view_box_minimal_y = 0.0, 0.0 view_box_maximal_x, view_box_maximal_y = 0.0, 0.0 for state in states_sequence: # Создание образа состояния аппарата state_view_angle = - state.coordinates[2] / math.pi * 180.0 state_view_center = \ state.coordinates[0] * coordinates_scaling, \ - state.coordinates[1] * coordinates_scaling state_view_position = \ state_view_center[0], \ state_view_center[1] - machine_view_width / 2.0 state_view = \ Rect( insert = state_view_position, size = self.__machine_view_size, fill = rgb(255, 255, 255), stroke = rgb(0, 0, 0), stroke_width = 1 ) state_view.rotate( state_view_angle, center = state_view_center ) # Добавление образа состояния аппарата к образу траектории drawing.add(state_view) if is_view_box_initialized: view_box_minimal_x, view_box_minimal_y = \ min(state_view_center[0], view_box_minimal_x), \ min(state_view_center[1], view_box_minimal_y) view_box_maximal_x, view_box_maximal_y = \ max(state_view_center[0], view_box_maximal_x), \ max(state_view_center[1], view_box_maximal_y) else: is_view_box_initialized = True view_box_minimal_x, view_box_minimal_y = \ state_view_center[0], \ state_view_center[1] view_box_maximal_x, view_box_maximal_y = \ state_view_center[0], \ state_view_center[1] # Настройка отображения образа траектории drawing.viewbox( minx = view_box_minimal_x - machine_radius, miny = view_box_minimal_y - machine_radius, width = view_box_maximal_x - view_box_minimal_x + machine_diameter, height = view_box_maximal_y - view_box_minimal_y + machine_diameter ) # Запись образа траектории в файл try: drawing.write(output_file) except: raise Exception() #!!!!! Генерировать хорошие исключения
Note, these require the svgwrite package (and optionally, the svglib package to convert to pdf). """ from __future__ import print_function import subprocess from svgwrite import Drawing filename = 'scaling_run_model.svg' color_phys = '#85C1E9' color_scaled = '#EC7063' main_font_size = 24 dwg = Drawing(filename, (2500, 2000), debug=True) top_text = dwg.add(dwg.g(font_size=main_font_size, style="font-family: arial;")) locs = ['NL Inputs', 'NL Outputs', 'NL Residuals'] x = 900 y = 50 delta_x = 400 vertical_locs = [] for loc in locs: top_text.add(dwg.text(loc, (x - len(loc)*4, y))) vertical_locs.append(x) x += delta_x legend_text = dwg.add(dwg.g(font_size=main_font_size, style="font-family: arial;"))
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 create_svg(file, surface, controls_sequence = None, sn = None): min_x, max_x = None, None min_y, max_y = None, None for polygon in surface.polygons: for vertex in polygon.vertices: if min_x is None: min_x, max_x = vertex[0], vertex[0] min_y, max_y = vertex[1], vertex[1] else: min_x, max_x = min(vertex[0], min_x), max(vertex[0], max_x) min_y, max_y = min(vertex[1], min_y), max(vertex[1], max_y) a = (min_x, max_x, min_y, max_y) b = (max_x - min_x, max_y - min_y, 10.0) surface_view = Drawing(size = (max_x - min_x, max_y - min_y)) def correct_vertex(vertex, surface_bounds, canvas_bounds): x_coordinate, y_coordinate = vertex left_bound, right_bound, bottom_bound, top_bound = surface_bounds canvas_width, canvas_height, canvas_padding = canvas_bounds x_coordinate_scaling = \ (canvas_width - 2.0 * canvas_padding) \ / (right_bound - left_bound) y_coordinate_scaling = \ (canvas_height - 2.0 * canvas_padding) \ / (top_bound - bottom_bound) x_coordinate = \ x_coordinate_scaling * (x_coordinate - left_bound) \ + canvas_padding y_coordinate = \ y_coordinate_scaling * (y_coordinate - bottom_bound) \ + canvas_padding y_coordinate = canvas_height - y_coordinate return x_coordinate, y_coordinate for polygon in surface.polygons: vertices = \ [correct_vertex((x_coordinate, y_coordinate), a, b) \ for x_coordinate, y_coordinate, z_coordinate \ in polygon.vertices] if surface.maximal_impossibility - surface.minimal_impossibility > 0.0: relative_impossibility = \ (polygon.impossibility - surface.minimal_impossibility) \ / (surface.maximal_impossibility - surface.minimal_impossibility) else: relative_impossibility = 0.0 fill_color_red_component = round(255.0 * relative_impossibility) fill_color_green_component = round(255.0 - 255.0 * relative_impossibility) fill_color_blue_component = 0 fill_color = \ rgb( fill_color_red_component, fill_color_green_component, fill_color_blue_component ) polygon_view = \ Polygon( vertices, stroke_width = 1, stroke = rgb(0, 0, 0), fill = fill_color ) surface_view.add(polygon_view) if sn is not None: for polygon_index, polygon in enumerate(surface.polygons): polygon_index_view = \ Text( str(polygon_index), insert = \ correct_vertex( (polygon.center[0], polygon.center[1]), a, b ) ) surface_view.add(polygon_index_view) # if sn is not None: # from svgwrite.text import Text # for state in sn: # polygon = state.polygon # polygon_number = sn[state] # polygon_center = polygon.center[0], polygon.center[1] # q,w = correct_vertex(polygon_center, a, b) # polygon_center_view = \ # Text( # str(polygon_number), # insert = (q+2,w+2), # style = "font-size: 50%; font-color: #808080" # ) # surface_view.add(polygon_center_view) if controls_sequence is not None: last_state = None last_polygon_center = None smoother = Smoother(surface = surface, smoothing_depth = 1) for state in controls_sequence: polygon = state.polygons_sequence[-1] polygon_center = polygon.center[0], polygon.center[1] polygon_center_view = \ Circle( correct_vertex(polygon_center, a, b), 2, stroke_width = 0, fill = rgb(0, 0, 0), ) surface_view.add(polygon_center_view) if last_state is not None: smoother.push_transfer( last_state.get_transfer(state) ) first_transfer_point, second_transfer_point = \ smoother.transfers_points_sequence[0] first_trek_view = \ Line( correct_vertex(last_polygon_center, a, b), correct_vertex( (first_transfer_point.coordinates[0], first_transfer_point.coordinates[1]), a, b ), stroke_width = 1, stroke = rgb(0, 0, 0), ) first_trek_end_view = \ Circle( correct_vertex( (first_transfer_point.coordinates[0], first_transfer_point.coordinates[1]), a, b ), 2, stroke_width = 0, fill = rgb(0, 0, 0), ) second_trek_view = \ Line( correct_vertex( (second_transfer_point.coordinates[0], second_transfer_point.coordinates[1]), a, b ), correct_vertex(polygon_center, a, b), stroke_width = 1, stroke = rgb(0, 0, 0), ) second_trek_start_view = \ Circle( correct_vertex( (second_transfer_point.coordinates[0], second_transfer_point.coordinates[1]), a, b ), 2, stroke_width = 0, fill = rgb(0, 0, 0), ) surface_view.add(first_trek_view) surface_view.add(first_trek_end_view) surface_view.add(second_trek_view) surface_view.add(second_trek_start_view) last_state = state last_polygon_center = polygon_center surface_view.write(file)
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()
Image.fromarray(np.asarray(skeletonized, dtype='uint8') * 255).save("skeletonized.png") g = im_to_graph(skeletonized) lines = list(partition_forest(g)) assert len(lines) == 1 line = lines.pop() smoothed = gaussian_filter1d(np.asarray(line, dtype=float), sigma=DEFAULT_SIGMA, axis=0) length = np.linalg.norm(np.diff(smoothed, axis=0), axis=1).sum() dwg = Drawing("line.svg") dwg.add(dwg.rect(size=drawn_arr.shape[::-1], fill="black")) dwg.add( dwg.polyline([tuple(pair[::-1]) for pair in smoothed], stroke="white", stroke_width=4, fill="none")) dwg.save(pretty=True, indent=2) def pad_dim(vals, pad, integer=True): vmin = vals.min() vmax = vals.max() vpad = (vmax - vmin) * pad if integer: vpad = int(np.ceil(vpad))
def _draw(self, d: svgwrite.Drawing, size: XY, offset: XY, year: int): min_size = min(size.x, size.y) year_size = min_size * 4.0 / 80.0 year_style = 'font-size:{}px; font-family:Arial;'.format(year_size) month_style = 'font-size:{}px; font-family:Arial;'.format(min_size * 3.0 / 80.0) day_style = 'dominant-baseline: central; font-size:{}px; font-family:Arial;'.format( min_size * 1.0 / 80.0) day_length_style = 'font-size:{}px; font-family:Arial;'.format( min_size * 1.0 / 80.0) d.add( d.text('{}'.format(year), insert=offset.tuple(), fill=self.poster.colors['text'], alignment_baseline="hanging", style=year_style)) offset.y += year_size size.y -= year_size count_x = 31 for month in range(1, 13): date = datetime.date(year, month, 1) (_, last_day) = calendar.monthrange(year, month) count_x = max(count_x, date.weekday() + last_day) cell_size = min(size.x / count_x, size.y / 36) spacing = XY((size.x - cell_size * count_x) / (count_x - 1), (size.y - cell_size * 3 * 12) / 11) dow = ["M", "T", "W", "T", "F", "S", "S"] for month in range(1, 13): date = datetime.date(year, month, 1) y = month - 1 y_pos = offset.y + (y * 3 + 1) * cell_size + y * spacing.y d.add( d.text(date.strftime("%B"), insert=(offset.x, y_pos - 2), fill=self.poster.colors['text'], alignment_baseline="hanging", style=month_style)) day_offset = date.weekday() while date.month == month: x = date.day - 1 x_pos = offset.x + (day_offset + x) * cell_size + x * spacing.x pos = (x_pos + 0.05 * cell_size, y_pos + 0.05 * cell_size) dim = (cell_size * 0.9, cell_size * 0.9) text_date = date.strftime("%Y-%m-%d") if text_date in self.poster.tracks_by_date: tracks = self.poster.tracks_by_date[text_date] length = sum([t.length for t in tracks]) color = self.color(self.poster.length_range_by_date, length, [t for t in tracks if t.special]) d.add(d.rect(pos, dim, fill=color)) d.add( d.text("{:.1f}".format(self.poster.m2u(length)), insert=(x_pos + cell_size / 2, y_pos + cell_size + cell_size / 2), text_anchor="middle", style=day_length_style, fill=self.poster.colors['text'])) else: d.add(d.rect(pos, dim, fill='#444444')) d.add( d.text(dow[date.weekday()], insert=(offset.x + (day_offset + x) * cell_size + cell_size / 2, y_pos + cell_size / 2), text_anchor="middle", alignment_baseline="middle", style=day_style)) date += datetime.timedelta(1)
def process(self): graphs = {} intervals = [] smooth = self.parameters.get("smooth", self.options["smooth"]["default"]) normalise_values = self.parameters.get( "normalise", self.options["normalise"]["default"]) completeness = convert_to_int( self.parameters.get("complete", self.options["complete"]["default"]), 0) graph_label = self.parameters.get("label", self.options["label"]["default"]) top = convert_to_int( self.parameters.get("top", self.options["top"]["default"]), 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" with self.source_file.open() as input: reader = csv.DictReader(input) item_key = "text" if "text" in reader.fieldnames else "item" date_key = "time" if "time" in reader.fieldnames else "date" value_key = "value" if "value" in reader.fieldnames else "frequency" for row in self.iterate_csv_items(self.source_file): if row[item_key] not in graphs: graphs[row[item_key]] = {} # make sure the months and days are zero-padded interval = row.get(date_key, "") 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_key]]: graphs[row[item_key]][interval] = 0 graphs[row[item_key]][interval] += float(row.get(value_key, 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 = 1 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 = 75 # 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 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 = Drawing(str(self.dataset.get_results_path()), size=(canvas_width + margin, canvas_height + (2 * margin)), style="font-family:monospace") # draw gridlines - vertical gridline_x = y_axis_width gridline_y = margin + 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 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") 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.75em", style="font-weight:bold;")) if re.match(r"^[0-9]{4}-[0-9]{2}", intervals[i]): 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%", "100%"), text=label2, transform="rotate(%f) skewX(%f)" % (-degrees(plane_obverse), degrees(plane_obverse)), text_anchor="middle", baseline_shift="-1.75em")) 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 graph_start_y = margin + 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 = 0 gridline_y = margin + 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=(y_axis_width, margin + canvas_height), end=(canvas_width, margin + canvas_height - x_axis_height), stroke="black", stroke_width=2)) if graph_label: canvas.add( Text(insert=((margin / 10), (margin / 2)), text=graph_label, style="font-size:2em;", alignment_baseline="hanging")) # and finally save the SVG canvas.save(pretty=True) self.dataset.finish(len(graphs))
def export_node(node, store, size=None): """Construct a SVG description for a workflow node. Args: node (NodeDef) store (dict of uid, def): elements definitions size (int, int): size of drawing in pixels Returns: (str) - SVG description of workflow node """ pfs = port_font_size # node size pr = port_radius pspace = pr * 9 nw = compute_node_width(node, node['name'], pspace) nh = label_font_size + 2 * pr + 2 * pfs + 2 + (2 * node_padding) # draw if size is None: size = (600, 600) paper = Drawing("workflow_node.svg", size, id="repr") lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="in_port") lg.add_stop_color(0, color='#3333ff') lg.add_stop_color(1, color='#2222ff') paper.defs.add(lg) lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="out_port") lg.add_stop_color(0, color='#ffff33') lg.add_stop_color(1, color='#9a9a00') paper.defs.add(lg) # body g = paper.add(paper.g()) # background lg = paper.linearGradient((0.5, 0), (0.5, 1.)) lg.add_stop_color(0, color='#8c8cff') lg.add_stop_color(1, color='#c8c8c8') paper.defs.add(lg) bg = paper.rect((-nw / 2, -nh / 2), (nw, nh), rx=node_padding, ry=node_padding, stroke_width=1) bg.stroke('#808080') bg.fill(lg) g.add(bg) # label style = ('font-size: %dpx; font-family: %s; ' 'text-anchor: middle' % (label_font_size, label_font)) frag = paper.tspan(node['name'], dy=[label_font_size // 3]) label = paper.text("", style=style, fill='#000000') label.add(frag) g.add(label) # ports port_style = ('font-size: %dpx; ' % pfs + 'font-family: %s; ' % label_font) onstyle = port_style + 'text-anchor: end' instyle = port_style + 'text-anchor: start' istyle = port_style + 'text-anchor: middle' nb = len(node['inputs']) py = -nh / 2 for i, pdef in enumerate(node['inputs']): px = i * pspace - pspace * (nb - 1) / 2 pg = g.add(paper.g()) pg.translate(px, py) idef = store.get(pdef['interface'], None) if idef is not None and 'url' in idef: link = pg.add(paper.a(href=idef['url'], target='_top')) else: link = pg port = paper.circle((0, 0), pr, stroke='#000000', stroke_width=1) port.fill("url(#in_port)") link.add(port) # port name frag = paper.tspan(pdef['name'], dy=[-2 * pr]) label = paper.text("", style=instyle, fill='#000000') label.rotate(-45) label.add(frag) pg.add(label) # port interface if idef is None: itxt = pdef['interface'] else: itxt = idef['name'] if len(itxt) > 10: itxt = itxt[:7] + "..." frag = paper.tspan(itxt, dy=[pr + pfs]) label = paper.text("", style=istyle, fill='#000000') label.add(frag) link.add(label) nb = len(node['outputs']) py = nh / 2 for i, pdef in enumerate(node['outputs']): px = i * pspace - pspace * (nb - 1) / 2 pg = g.add(paper.g()) pg.translate(px, py) idef = store.get(pdef['interface'], None) if idef is not None and 'url' in idef: link = pg.add(paper.a(href=idef['url'], target='_top')) else: link = pg port = paper.circle((0, 0), pr, stroke='#000000', stroke_width=1) port.fill("url(#out_port)") link.add(port) # port name frag = paper.tspan(pdef['name'], dy=[2 * pr + pfs // 2]) label = paper.text("", style=onstyle, fill='#000000') label.rotate(-45) label.add(frag) pg.add(label) # port interface if idef is None: itxt = pdef['interface'] else: itxt = idef['name'] if len(itxt) > 10: itxt = itxt[:7] + "..." frag = paper.tspan(itxt, dy=[-pr - 2]) label = paper.text("", style=istyle, fill='#000000') label.add(frag) link.add(label) # reformat whole drawing to fit screen xmin = -nw / 2 - draw_padding / 10. xmax = +nw / 2 + draw_padding / 10. if len(node['inputs']) == 0: inames_extend = 0 else: inames = [(len(pdef['name']), pdef['name']) for pdef in node['inputs']] inames_extend = string_size(sorted(inames)[-1][1], pfs) * 0.7 + pfs ymin = -nh / 2 - pr - inames_extend - draw_padding / 10. if len(node['outputs']) == 0: onames_extend = 0 else: onames = [(len(pdef['name']), pdef['name']) for pdef in node['outputs']] onames_extend = string_size(sorted(onames)[-1][1], pfs) * 0.7 + pfs ymax = +nh / 2 + pr + onames_extend + draw_padding / 10. w = float(size[0]) h = float(size[1]) ratio = max((xmax - xmin) / w, (ymax - ymin) / h) xsize = ratio * w ysize = ratio * h bb = (xmin * xsize / (xmax - xmin), ymin * ysize / (ymax - ymin), xsize, ysize) paper.viewbox(*bb) return paper.tostring(), bb
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 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 plot(self, svg: Drawing) -> None: recolor: Optional[str] = None if isinstance(self.color, list): linear_gradient: LinearGradient = svg.linearGradient( self.map_((0, self.max_y)), self.map_((0, 1)), gradientUnits="userSpaceOnUse", ) for index, color in enumerate(self.color): linear_gradient.add_stop_color( index / (len(self.color) - 1), color.hex ) gradient: LinearGradient = svg.defs.add(linear_gradient) recolor = gradient.get_funciri() if isinstance(self.color, Color): recolor = self.color.hex svg.add( svg.rect( insert=(0, 0), size=("100%", "100%"), rx=None, ry=None, fill=self.background_color.hex, ) ) self.draw_grid(svg) last_text_y = 0 for xs, ys, color, title in self.data: if recolor: color = recolor assert len(xs) == len(ys) xs_second: list[float] = [ (x - self.min_x).total_seconds() for x in xs ] points = [] for index, x in enumerate(xs_second): y = ys[index] mapped: np.ndarray = map_array( np.array((x, y)), np.array((self.min_x_second, self.max_y)), np.array((self.max_x_second, self.min_y)), self.canvas.workspace[0], self.canvas.workspace[1], ) points.append(mapped) previous_point: Optional[np.ndarray] = None for point in points: if previous_point is not None: line: Line = svg.line( (previous_point[0], previous_point[1]), (point[0], point[1]), stroke=self.background_color.hex, stroke_width=6, ) svg.add(line) line: Line = svg.line( (previous_point[0], previous_point[1]), (point[0], point[1]), stroke=color, stroke_width=2, ) svg.add(line) previous_point = point for point in points: svg.add( svg.circle( (point[0], point[1]), 5.5, fill=self.background_color.hex, ) ) svg.add(svg.circle((point[0], point[1]), 3.5, fill=color)) title: str text_y = max(last_text_y + 20, point[1] + 6) self.text(svg, (point[0] + 15, text_y), title.upper(), color) last_text_y = text_y with Path(svg.filename).open("w+") as output_file: svg.write(output_file)
from svgwrite import Drawing from xml.etree import ElementTree from pathlib import Path from cerebellum_value_map.shape import PathShape from cerebellum_value_map.path_code import Move, Line dirname = Path('results') dirname.mkdir(exist_ok=True) shape = PathShape(Move([100, 100]), Line([120, 140]), Line([160, 100])) drawing_orig = Drawing(dirname.joinpath('shape.svg'), size=(200, 200), stroke='black', stroke_width=1) drawing_orig.add(shape.get_svg()) drawing_orig.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 process(self): """ Render an SVG histogram/bar chart using a previous frequency analysis as input. """ self.dataset.update_status("Reading source file") header = self.parameters.get("header", self.options["header"]["default"]) max_posts = 0 # collect post numbers per month intervals = {} for post in self.iterate_csv_items(self.source_file): intervals[post["date"]] = int(post["frequency"]) max_posts = max(max_posts, int(post["frequency"])) if len(intervals) <= 1: self.dataset.update_status("Not enough data available for a histogram; need more than one time series.") self.dataset.finish(0) return self.dataset.update_status("Cleaning up data") (missing, intervals) = pad_interval(intervals) # create histogram self.dataset.update_status("Drawing histogram") # you may change the following four variables to adjust the graph dimensions width = 1024 height = 786 y_margin = 75 x_margin = 50 x_margin_left = x_margin * 2 tick_width = 5 fontsize_normal = int(height / 40) fontsize_small = int(height / 75) # better don't touch the following line_width = round(width / 512) y_margin_top = 150 if header else 50 y_height = height - (y_margin + y_margin_top) x_width = width - (x_margin + x_margin_left) canvas = Drawing(filename=str(self.dataset.get_results_path()), size=(width, height), style="font-family:monospace;font-size:%ipx" % fontsize_normal) # normalize the Y axis to a multiple of a power of 10 magnitude = pow(10, len(str(max_posts)) - 1) # ew max_neat = math.ceil(max_posts / magnitude) * magnitude self.dataset.update_status("Max (normalized): %i (%i) (magnitude: %i)" % (max_posts, max_neat, magnitude)) # draw border canvas.add(Rect( insert=(0, 0), size=(width, height), stroke="#000", stroke_width=line_width, fill="#FFF" )) # draw header on a black background if needed if header: if len(header) > 40: header = header[:37] + "..." header_rect_height = (y_margin_top / 1.5) header_fontsize = (width / len(header)) header_container = SVG(insert=(0, 0), size=(width, header_rect_height)) header_container.add(Rect( insert=(0, 0), size=(width, header_rect_height), fill="#000" )) header_container.add(Text( insert=("50%", "50%"), text=header, dominant_baseline="middle", text_anchor="middle", fill="#FFF", style="font-size:%i" % header_fontsize )) canvas.add(header_container) # horizontal grid lines for i in range(0, 10): offset = (y_height / 10) * i canvas.add(Line( start=(x_margin_left, y_margin_top + offset), end=(width - x_margin, y_margin_top + offset), stroke="#EEE", stroke_width=line_width )) # draw bars item_width = (width - (x_margin + x_margin_left)) / len(intervals) item_height = (height - y_margin - y_margin_top) bar_width = item_width * 0.9 x = x_margin_left + (item_width / 2) - (bar_width / 2) if bar_width >= 8: arc_adjust = max(8, int(item_width / 5)) / 2 else: arc_adjust = 0 for interval in intervals: posts = int(intervals[interval]) bar_height = ((posts / max_neat) * item_height) self.dataset.update_status("%s: %i posts" % (interval, posts)) bar_top = height - y_margin - bar_height bar_bottom = height - y_margin if bar_height == 0: x += item_width continue bar = Path(fill="#000") bar.push("M %f %f" % (x, bar_bottom)) bar.push("L %f %f" % (x, bar_top + (arc_adjust if bar_height > arc_adjust else 0))) if bar_height > arc_adjust > 0: control = (x, bar_top) bar.push("C %f %f %f %f %f %f" % (*control, *control, x + arc_adjust, bar_top)) bar.push("L %f %f" % (x + bar_width - arc_adjust, height - y_margin - bar_height)) if bar_height > arc_adjust > 0: control = (x + bar_width, bar_top) bar.push("C %f %f %f %f %f %f" % (*control, *control, x + bar_width, bar_top + arc_adjust)) bar.push("L %f %f" % (x + bar_width, height - y_margin)) bar.push("Z") canvas.add(bar) x += item_width # draw X and Y axis canvas.add(Line( start=(x_margin_left, height - y_margin), end=(width - x_margin, height - y_margin), stroke="#000", stroke_width=2 )) canvas.add(Line( start=(x_margin_left, y_margin_top), end=(x_margin_left, height - y_margin), stroke="#000", stroke_width=2 )) # draw ticks on Y axis for i in range(0, 10): offset = (y_height / 10) * i canvas.add(Line( start=(x_margin_left - tick_width, y_margin_top + offset), end=(x_margin_left, y_margin_top + offset), stroke="#000", stroke_width=line_width )) # draw ticks on X axis for i in range(0, len(intervals)): offset = (x_width / len(intervals)) * (i + 0.5) canvas.add(Line( start=(x_margin_left + offset, height - y_margin), end=(x_margin_left + offset, height - y_margin + tick_width), stroke="#000", stroke_width=line_width )) # prettify # y labels origin = (x_margin_left / 2) step = y_height / 10 for i in range(0, 11): label = str(int((max_neat / 10) * i)) labelsize = (len(label) * fontsize_normal * 1.25, fontsize_normal) label_x = origin - (tick_width * 2) label_y = height - y_margin - (i * step) - (labelsize[1] / 2) label_container = SVG( insert=(label_x, label_y), size=(x_margin_left / 2, x_margin_left / 5) ) label_container.add(Text( insert=("100%", "50%"), text=label, dominant_baseline="middle", text_anchor="end" )) canvas.add(label_container) # x labels label_width = max(fontsize_small * 6, item_width) label_x = x_margin_left label_y = height - y_margin + (tick_width * 2) next = 0 for interval in intervals: if len(interval) == 7: label = month_abbr[int(interval[5:7])] + "\n" + interval[0:4] elif len(interval) == 10: label = str(int(interval[8:10])) + month_abbr[int(interval[5:7])] + "\n" + interval[0:4] else: label = interval.replace("-", "\n") if label_x > next: shift = 0 for line in label.split("\n"): label_container = SVG( insert=(label_x + (item_width / 2) - (label_width / 2), label_y + (tick_width * 2)), size=(label_width, y_margin), overflow="visible") label_container.add(Text( insert=("50%", "0%"), text=line, dominant_baseline="middle", text_anchor="middle", baseline_shift=-shift )) shift += fontsize_small * 2 canvas.add(label_container) next = label_x + (label_width * 0.9) label_x += item_width # 4cat logo label = "made with 4cat - 4cat.oilab.nl" footersize = (fontsize_small * len(label) * 0.7, fontsize_small * 2) footer = SVG(insert=(width - footersize[0], height - footersize[1]), size=footersize) footer.add(Rect(insert=(0, 0), size=("100%", "100%"), fill="#000")) footer.add(Text( insert=("50%", "50%"), text=label, dominant_baseline="middle", text_anchor="middle", fill="#FFF", style="font-size:%i" % fontsize_small )) canvas.add(footer) canvas.save(pretty=True) self.dataset.update_status("Finished") self.dataset.finish(len(intervals))
class Figure(object): def __init__(self): self.elements = [] self.extra = [] self.ticks = [] self.svg = Drawing() def add(self, element, xy): self.elements.append(element) self.extra.append(xy) return element def add_genome(self, xy, name, length): genome = self.add(Genome(name, end=length), xy) return genome def add_genome_from_file(self, xy, file): file_format = file.split(".")[-1] if file_format in "gb|genbank": for record in SeqIO.parse(file, "genbank"): genome = self.add( Genome( name=record.id, end=len(record.seq), ), xy) for feature in record.features: if feature.type == "CDS": name = feature.qualifiers["locus_tag"][0] genome.add_feature( name=name, start=feature.location.start, end=feature.location.end, strand=feature.location.strand, ) break return genome def add_ticks(self, xy, ticks, labels): tick = Axis(ticks, labels) self.elements.append(tick) self.extra.append(xy) return tick def homology(self, name, color): h**o = Homology(name, color) self.elements.append(h**o) self.extra.append([0, 0]) return h**o def save(self, filename="out.svg"): scale = 1200.0 / max([ i.length - i.break_length for i in self.elements if isinstance(i, Genome) ]) genomes = [] for i, e in enumerate(self.elements): if isinstance(e, Genome): genomes.append( e.plot(scale=scale, x=self.extra[i][0], y=self.extra[i][1], stroke="black", stroke_width=2)) continue self.svg.add( e.plot(scale=scale, x=self.extra[i][0], y=self.extra[i][1], stroke="black", stroke_width=2)) for g in genomes: self.svg.add(g) self.svg.saveas(filename, pretty=True)
# create svg dwg = Drawing("test.svg", size=(width,height)) dwg.defs.add(dwg.style(css)) data = read_csv(indata) labels = data.iloc[:,0] cols = data.iloc[:,1:] ncols = cols.shape[1] nlines = ncols - 1 colspace = h - nlines*200 colwidth = colspace / float(ncols) collocs = (arange(ncols) + 1)*colwidth # pad g = dwg.add(dwg.g(transform="translate(%i,%i)" % (padding,padding))) title = g.add(dwg.g()) for i in range(ncols): if i==0: title.add(dwg.text(cols.columns[i], insert=(collocs[i],0), text_anchor='end', font_size='%ipx' % titlefont)) elif i==ncols-1: title.add(dwg.text(cols.columns[i], insert=(collocs[i],0), font_size='%ipx' % titlefont)) g = g.add(dwg.g(transform="translate(0,%i)" % (titlefont+fontsize))) # loop over and add labels vmin = min(cols.values) vmax = max(cols.values) def scale(val, src=(vmin, vmax), dst=(0, h)): return ((float(val) - src[0]) / (src[1]-src[0])) * (dst[1]-dst[0]) + dst[0]