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 draw_multi_transcript_overlay(config, gene, vmarkers=None, window_buffer=0, plots=None, log=DEVNULL): vmarkers = [] if vmarkers is None else vmarkers plots = [] if plots is None else plots canvas = Drawing( size=(config.width, 1000)) # just set the height for now and change later width = config.width - config.left_margin - config.right_margin - config.overlay_left_label - config.padding labels = LabelMapping() # keep labels consistent within the drawing all_exons = set() colors = dict() for us_tx in gene.transcripts: for ex in us_tx.exons: all_exons.add(ex) colors[ ex] = config.exon1_color if us_tx.is_best_transcript else config.exon2_color for spl_tx in us_tx.transcripts: for translation in spl_tx.translations: for dom in translation.domains: labels.set_key(dom.name, dom.name) genomic_min = min([max([gene.start - window_buffer, 1])] + [m.start for m in vmarkers] + [p.xmin for p in plots if p.xmin]) genomic_max = max([gene.end + window_buffer] + [m.end for m in vmarkers] + [p.xmax for p in plots if p.xmax]) mapping = generate_interval_mapping(all_exons, width, config.exon_intron_ratio, config.exon_min_width, min_inter_width=config.min_width, start=genomic_min, end=genomic_max) main_group = canvas.g(class_='overlay') x = config.overlay_left_label + config.padding y = config.marker_top_margin for plot in plots: if plot.points: plot_group = draw_scatter(config, canvas, plot, mapping, log=log) main_group.add(plot_group) plot_group.translate(x, y) y += plot.height + config.padding * 2 regular_transcripts = sorted( [us_tx for us_tx in gene.transcripts if not us_tx.is_best_transcript], key=lambda x: x.name) for us_tx in regular_transcripts: group_element = draw_exon_track(config, canvas, us_tx, mapping, colors=colors, genomic_min=genomic_min, genomic_max=genomic_max) main_group.add(group_element) group_element.translate(x, y) text_element = canvas.text( us_tx.name, insert=(x - config.padding, y + config.track_height / 2 + config.font_central_shift_ratio * config.label_font_size), fill=config.label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='end'), class_='label') main_group.add(text_element) y += config.padding + config.track_height best_transcripts = sorted( [us_tx for us_tx in gene.transcripts if us_tx.is_best_transcript], key=lambda x: x.name) for us_tx in best_transcripts: for spl_tx in us_tx.transcripts: labels[us_tx.name] = spl_tx group_element = draw_ustranscript(config, canvas, us_tx, mapping=mapping, colors=colors, labels=labels) main_group.add(group_element) group_element.translate(x, y) y += config.padding + group_element.height y += config.marker_bottom_margin # now draw the breakpoints overtop for marker in sorted(vmarkers): px_itvl = Interval( mapping.convert_ratioed_pos(marker.start).start, mapping.convert_ratioed_pos(marker.end).end) group_element = draw_vmarker(config, canvas, marker, px_itvl.length(), y, label=marker.name) group_element.translate(x + px_itvl.start, 0) main_group.add(group_element) main_group.translate(config.left_margin, config.top_margin) y += config.bottom_margin canvas.add(main_group) canvas.attribs['height'] = y return canvas
def draw_text(self, svg: svgwrite.Drawing, text: str, point, occupied: Occupied, fill: Color, size: float = 10.0, out_fill=Color("white"), out_opacity=0.5, out_fill_2: Optional[Color] = None, out_opacity_2=1.0): """ Drawing text. ###### ### outline 2 #------# --- outline 1 #| Text |# #------# ###### """ length = len(text) * 6 if occupied: is_occupied: bool = False for i in range(-int(length / 2), int(length / 2)): if occupied.check((int(point[0] + i), int(point[1] - 4))): is_occupied = True break if is_occupied: return for i in range(-int(length / 2), int(length / 2)): for j in range(-12, 5): occupied.register((int(point[0] + i), int(point[1] + j))) # svg.add(svg.rect((point[0] + i, point[1] + j), (1, 1))) if out_fill_2: svg.add( svg.text(text, point, font_size=size, text_anchor="middle", font_family=DEFAULT_FONT, fill=out_fill_2.hex, stroke_linejoin="round", stroke_width=5, stroke=out_fill_2.hex, opacity=out_opacity_2)) if out_fill: svg.add( svg.text(text, point, font_size=size, text_anchor="middle", font_family=DEFAULT_FONT, fill=out_fill.hex, stroke_linejoin="round", stroke_width=3, stroke=out_fill.hex, opacity=out_opacity)) svg.add( svg.text(text, point, font_size=size, text_anchor="middle", font_family=DEFAULT_FONT, fill=fill.hex)) self.y += 11
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;")) legend_text.add(dwg.text('Phys', (x-300, y-10), fill=color_phys)) legend_text.add(dwg.text('Scaled', (x-300, y+20), fill=color_scaled)) v_lines = dwg.add(dwg.g(stroke_width=7.0, stroke=color_phys, fill='none')) v_lines_scaled = dwg.add(dwg.g(stroke_width=7.0, stroke=color_scaled, fill='none')) for loc in vertical_locs: v_lines.add(dwg.line(start=(loc, y+15), end=(loc, 1100))) extra_text = dwg.add(dwg.g(font_size=main_font_size - 3, style="font-family: arial;")) extra_text.add(dwg.text('Unit Conversion', (vertical_locs[0] + 10, 650)))
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): """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. 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[0] = svg_attributes.get("width", dimensions[0]) dimensions[1] = 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)) # 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 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.to_year, self.poster.years.from_year - 1,-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(self, dr: 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 = f"font-size:{year_size}px; font-family:Arial;" month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;" day_style = f"dominant-baseline: central; font-size:{min_size * 1.0 / 80.0}px; font-family:Arial;" day_length_style = f"font-size:{min_size * 1.0 / 80.0}px; font-family:Arial;" dr.add( dr.text( f"{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, ) # chinese weekday key number is the third. keyword_num = 0 if locale.getlocale()[0] == "zh_CN": keyword_num = 2 # first character of localized day names, starting with Monday. dow = [ locale.nl_langinfo(day)[keyword_num].upper() for day in [ locale.DAY_2, locale.DAY_3, locale.DAY_4, locale.DAY_5, locale.DAY_6, locale.DAY_7, locale.DAY_1, ] ] 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 dr.add( dr.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 + 1.15 * 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]) has_special = len([t for t in tracks if t.special]) > 0 color = self.color( self.poster.length_range_by_date, length, has_special ) dr.add(dr.rect(pos, dim, fill=color)) dr.add( dr.text( utils.format_float(self.poster.m2u(length)), insert=( pos[0] + cell_size / 2, pos[1] + cell_size + cell_size / 2, ), text_anchor="middle", style=day_length_style, fill=self.poster.colors["text"], ) ) else: dr.add(dr.rect(pos, dim, fill="#444444")) dr.add( dr.text( dow[date.weekday()], insert=( offset.x + (day_offset + x) * cell_size + cell_size / 2, pos[1] + cell_size / 2, ), text_anchor="middle", alignment_baseline="middle", style=day_style, ) ) date += datetime.timedelta(1)
def plot(self, x, y, scale, break_width=10, **extra): svg = Drawing() break_length = 0 fragments = self.get_fragments() g = svg.g() for i in range(len(fragments)): f = fragments[i] if i > 0: break_length += f.start - fragments[ i - 1].end - break_width / scale p1 = [x + (f.start - break_length) * scale, y] p2 = [x + (f.end - break_length) * scale, y] f.position = p1 line1 = svg.line(start=p1, end=p2, **extra) line2 = svg.line(start=[p1[0], p1[1] - 5], end=[p1[0], p1[1] + 5], **extra) text2 = svg.text(text=f.start, insert=(p1[0], p1[1] + 10), font_size=8, font_family="Arial", transform='rotate(%s, %s, %s)' % (90, p1[0], p1[1] + 10)) line3 = svg.line(start=[p2[0], p1[1] - 5], end=[p2[0], p1[1] + 5], **extra) text3 = svg.text(text=f.end, insert=(p2[0], p2[1] + 10), font_size=8, font_family="Arial", transform='rotate(%s, %s, %s)' % (90, p2[0], p1[1] + 10)) g.add(line1) g.add(line2) g.add(text2) g.add(line3) g.add(text3) text1 = svg.text(text=self.name, insert=(10, y), font_size=10, font_family="Arial") g.add(text1) text2 = svg.text(text="%s nt" % self.length, insert=(x / 2.0 - 10, y + 12), font_size=8, font_family="Arial") g.add(text2) for e in self.features: if e.frag_genome: patch, text = e.plot(scale=scale) g.add(patch) g.add(text) return g
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]) /
def parse_shape(shape, i, gstates): # see https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PDF32000_2008.pdf output_filename = "page%s.svg" % i dwg = Drawing(output_filename, profile='tiny') fill = "none" stroke = rgb(0, 0, 0) stroke_width = 4 stroke_dasharray = None stroke_miterlimit = None transform = (1, 0, 0, 1, 0, 0) shapes_stack = [] d = "" paths = [] for line in shape.split("\n"): line = line.strip() parts = line.split(" ") nums = [] for part in parts: try: nums.append(float(part)) except ValueError: pass if endswith(line, 'BDC'): continue elif endswith(line, 'Tj'): text = ' '.join(parts[:-1]) text = text.split('Tj')[0] group = Group(transform="matrix({})".format(' '.join( [str(d) for d in transform]))) group.add(dwg.text(text)) shapes_stack.append(group) elif endswith(line, 'Tc'): pass # not yet implemented elif endswith(line, 'Tm'): for i, part in enumerate(parts): if part == 'Tm': transform = [float(d) for d in parts[i - 6:i]] if part == 'Tw': word_spacing = parts[i - 1] if part == 'Tc': char_spacing = parts[i - 1] elif endswith(line, 'q'): # q - start stack continue elif endswith(line, 're'): # rectangle vals = {'insert': (nums[0], nums[1]), 'size': (nums[2], nums[3])} if fill: vals['fill'] = fill if stroke: vals['stroke'] = stroke shapes_stack.append(dwg.rect(**vals)) elif endswith(line, 'n'): # - clipping path continue elif endswith(line, 'RG'): # set stroke color stroke = rgb(*nums[0:3]) elif endswith(line, 'K'): stroke = rgb(*cmyk(*nums[0:4])) elif endswith(line, 'J'): # not sure how to implement cap styles continue elif endswith(line, 'cm'): # current transformation matrix transform = nums[0:6] elif endswith(line, 'F') or endswith(line, 'f'): # fill fill = rgb(*nums[0:3]) elif endswith(line, 'm'): # move to d += " M " + format_pointstr(parts[0:2]) elif endswith(line, ' c'): # curve d += " C " + format_pointstr(parts[0:6]) elif endswith(line, 'v'): # append to bezier curve d += " S " + format_pointstr(parts[0:4]) elif endswith(line, 'y'): d += " C " + format_pointstr(parts[0:4]) + " " + format_pointstr( parts[2:4]) elif endswith(line, 'l'): # line to d += " L " + format_pointstr(parts[0:2]) elif endswith(line, 'h'): # make sure it's a closed path continue elif endswith(line, 'S'): # stroke to 4-unit width continue elif endswith(line, 'Q'): # end stack (draw) # apply transformation... for shape in shapes_stack: dwg.add(shape) if len(d) > 0: paths.append(d + " Z") d = " ".join( [transform_str(p, transform) for p in d.split(" ")]) vals = {'d': d} vals['stroke-width'] = stroke_width if fill: vals['fill'] = fill if stroke: vals['stroke'] = stroke if stroke_dasharray: vals['stroke-dasharray'] = stroke_dasharray if stroke_miterlimit: vals['stroke-miterlimit'] = stroke_miterlimit dwg.add(dwg.path(**vals)) d = '' shapes_stack = [] elif endswith(line, 'gs'): key = parts[0] if key not in gstates: print("could not find state %s in dictionary") state = gstates[key] # color blending not yet implemented pass elif endswith(line, 'w'): stroke_width = nums[0] elif endswith(line, 'd'): fullstring = " ".join(parts[:-1]) parsed = match('([\w\s\d]+)\[([\d\s]+)\](\d)', fullstring) if parsed: line_props = parsed.group(1) if line_props.find('M') >= 0: m_part = line_props.split('M')[0].split(' ')[-2] stroke_miterlimit = float(m_part) if line_props.find('w') >= 0: m_part = line_props.split('w')[0].split(' ')[-2] stroke_width = float(m_part) stroke_dasharray = parsed.group(2) offset = float( parsed.group(3)) # throw away the offset, it doesn't # convert nicely to svg elif endswith(line, 'M'): stroke_miterlimit = nums[0] else: print("not sure what to do with %s" % line) dwg.save() return paths
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'] 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;")) legend_text.add(dwg.text('Phys', (x - 300, y - 10), fill=color_phys)) legend_text.add(dwg.text('Scaled', (x - 300, y + 20), fill=color_scaled)) v_lines = dwg.add(dwg.g(stroke_width=7.0, stroke=color_phys, fill='none')) v_lines_scaled = dwg.add( dwg.g(stroke_width=7.0, stroke=color_scaled, fill='none')) for loc in vertical_locs: v_lines.add(dwg.line(start=(loc, y + 15), end=(loc, 1200)))
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)
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 legend_text = dwg.add( dwg.g(font_size=main_font_size, style="font-family: arial;")) legend_text.add(dwg.text('Phys', (x - 1500, y - 10), fill=color_phys)) legend_text.add(dwg.text('Scaled', (x - 1500, y + 20), fill=color_scaled)) v_lines = dwg.add(dwg.g(stroke_width=7.0, stroke=color_phys, fill='none')) v_lines_scaled = dwg.add( dwg.g(stroke_width=7.0, stroke=color_scaled, fill='none')) for loc in vertical_locs: v_lines.add(dwg.line(start=(loc, y + 15), end=(loc, 1200)))
key_font = ImageFont.truetype(sys_font, font_main_size) sec_font = ImageFont.truetype(sys_font, font_sub_size) key_style = f'font-size: {font_main_size}px; font-family:{web_font}' sec_style = f'font-size: {font_sub_size}px; font-family:{web_font}' for y in range(8): for x in range(5): idx = y * 5 + x key = primary[idx] alp = alpha[idx] bta = beta[idx] line_height = 2.5 if idx not in special_keys else 1.5 key_fill = alp_color if key == str_alp else bta_color if key == str_bta else main_color pos = tuple(map(operator.add, padding, (x * 15 * px, y * 15 * py))) size = (15 * px, 15 * py) button = dwg.add(dwg.g()) txt_size = key_font.getsize(key) alp_size = sec_font.getsize(alp) bet_size = sec_font.getsize(bta) txt_pos = (pos[0] + (size[0] - txt_size[0]) / 2, pos[1] + size[1] - txt_size[1] / line_height) alp_pos = (pos[0] + 5, pos[1] + (size[1] / 3.5) + (alp_size[1] / 10)) bet_pos = (pos[0] + (size[0] - bet_size[0] - 5), alp_pos[1]) button.add(dwg.rect(pos, size, fill='white', stroke='black')) button.add(dwg.text(key, txt_pos, style=key_style, fill=key_fill)) button.add(dwg.text(alp, alp_pos, style=sec_style, fill=alp_color)) button.add(dwg.text(bta, bet_pos, style=sec_style, fill=bta_color)) dwg.save()
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 legend_text = dwg.add(dwg.g(font_size=main_font_size, style="font-family: arial;")) legend_text.add(dwg.text('Phys', (x-1500, y-10), fill=color_phys)) legend_text.add(dwg.text('Scaled', (x-1500, y+20), fill=color_scaled)) v_lines = dwg.add(dwg.g(stroke_width=7.0, stroke=color_phys, fill='none')) v_lines_scaled = dwg.add(dwg.g(stroke_width=7.0, stroke=color_scaled, fill='none')) for loc in vertical_locs: v_lines.add(dwg.line(start=(loc, y+15), end=(loc, 1200))) extra_text = dwg.add(dwg.g(font_size=main_font_size - 3, style="font-family: arial;")) extra_text.add(dwg.text('fwd', (vertical_locs[5] + 55, 820)))
def plot(self, shape="arrow", strand=True, scale=1 / 2000, **extra): height = self.height if strand: y1 = self.frag_genome.position[ 1] - height / 2.0 - self.strand * height y2 = y1 + height else: y1 = self.frag_genome.position[1] - height / 2.0 y2 = self.frag_genome.position[1] + height / 2.0 if shape == "rect": p1 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale, y1 ] p2 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale, y1 ] p3 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale, (y1 + y2) / 2 ] p4 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale, y2 ] p5 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale, y2 ] if shape == "arrow": if self.strand == 1: p1 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale, y1 ] p2 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale - height / 3, y1 ] p3 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale, (y1 + y2) / 2 ] p4 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale - height / 3, y2 ] p5 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale, y2 ] else: p1 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale, y1 ] p2 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale + height / 3, y1 ] p3 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale, (y1 + y2) / 2 ] p4 = [ self.frag_genome.position[0] + (self.start - self.frag_genome.start) * scale + height / 3, y2 ] p5 = [ self.frag_genome.position[0] + (self.end - self.frag_genome.start) * scale, y2 ] if self.strand * p2[0] < self.strand * p1[0]: p2 = p1 p4 = p5 self.position = p1 if self.strand == -1: self.position = p3 svg = Drawing() if self.parent["Homology"]: patch = svg.polygon(points=[p1, p2, p3, p4, p5], fill=self.parent["Homology"].color) else: patch = svg.polygon(points=[p1, p2, p3, p4, p5], fill="white", stroke="black") t = [(p1[0] + p3[0]) / 2, p1[1] - 1] a = 1 if strand and self.strand == -1: t = [(p1[0] + p3[0]) / 2, p5[1] + 5] a = -1 text = svg.text(text=self.name, insert=t, font_size="8", transform='rotate(%s, %s, %s)' % (-45 * a, t[0], t[1])) return patch, text
class SvgRenderer(StreamRenderer): """ Draws the board like an SVG image (best representation for web) """ __rend_name__ = 'svg' DEFAULT_CELL_SIZE_IN_PIXELS = 15 BOLD_EVERY = 5 GRID_STROKE_WIDTH = 1 GRID_BOLD_STROKE_WIDTH = 2 @property def clues_font_size(self): """The size of the descriptions text""" return self.cell_size * 0.6 def __init__(self, board=None, stream=stdout, size=DEFAULT_CELL_SIZE_IN_PIXELS): super(SvgRenderer, self).__init__(board, stream) # decrease startup time when do not need this renderer from svgwrite import Drawing self.cell_size = size self.color_symbols = dict() self.drawing = Drawing(size=(self.full_width + self.cell_size, self.full_height + self.cell_size)) self._add_definitions() def _color_id_by_name(self, color): color_id = self.board.color_id_by_name(color) if color_id: return color_id if is_list_like(color): return from_two_powers( self.board.color_id_by_name(single_color) for single_color in color) return None def _add_symbol(self, id_, color, *parts, **kwargs): drawing = self.drawing symbol = drawing.symbol(id_=id_, **kwargs) for part in parts: symbol.add(part) if color is not None: # SPACE is already an ID if color not in (SPACE, SPACE_COLORED): if self.is_colored: color = self._color_id_by_name(color) self.color_symbols[color] = id_ drawing.defs.add(symbol) def _add_definitions(self): drawing = self.drawing # dynamic style rules drawing.defs.add( drawing.style( 'g.grid-lines line {stroke-width: %i} ' 'g.grid-lines line.bold {stroke-width: %i} ' 'g.header-clues text, g.side-clues text {font-size: %f} ' % ( self.GRID_STROKE_WIDTH, self.GRID_BOLD_STROKE_WIDTH, self.clues_font_size, ))) self._add_colors_def() self._add_symbol('check', None, drawing.circle(r=40, stroke_width=10, center=(50, 50)), drawing.polyline(stroke_width=12, points=[(35, 35), (35, 55), (75, 55)], transform='rotate(-45 50 50)'), stroke='green', fill='none') self.check_icon_size = 100 def _add_colors_def(self): drawing = self.drawing white_color = Color.white().name cell_size = self.cell_size rect_size = (cell_size, cell_size) upper_triangle_points = ((0, 0), (0, cell_size), (cell_size, 0)) lower_triangle_points = ((0, cell_size), (cell_size, 0), (cell_size, cell_size)) # three_colored_flag_rect_size = (cell_size / 3, cell_size) # three_colored_flag_insert_points = [(0, 0), (cell_size / 3, 0), (2 * cell_size / 3, 0)] three_color_triangle_size = round(cell_size * ((1 / 2)**0.5), 2) three_color_triangle_coord = round( cell_size - three_color_triangle_size, 2) three_colors_upper_points = [(0, 0), (0, three_color_triangle_size), (three_color_triangle_size, 0)] three_colors_lower_points = [ (cell_size, three_color_triangle_coord), (three_color_triangle_coord, cell_size), (cell_size, cell_size), ] # rendering should be predictable colors = [] if self.is_colored: for color_name in sorted(self.board.color_map): colors.append((color_name, self._color_from_name(color_name))) space_color = SPACE_COLORED else: colors.append((BOX, 'black')) space_color = SPACE for color_name, fill_color in colors: if color_name != white_color: self._add_symbol( 'color-%s' % color_name, color_name, drawing.rect( size=rect_size, fill=fill_color, )) if self.is_colored: for (color_name, fill_color), (color_name2, fill_color2) in combinations(colors, 2): LOG.info('Transient symbol: %s, %s + %s, %s', color_name, fill_color, color_name2, fill_color2) color_tuple = (color_name, color_name2) self._add_symbol( 'x2-%s' % '-'.join(map(str, color_tuple)), color_tuple, drawing.polygon( points=upper_triangle_points, fill=fill_color, ), drawing.polygon( points=lower_triangle_points, fill=fill_color2, ), ) for (color_name, fill_color), (color_name2, fill_color2), (color_name3, fill_color3) in combinations( colors, 3): LOG.info('Transient symbol: %s, %s + %s, %s + %s, %s', color_name, fill_color, color_name2, fill_color2, color_name3, fill_color3) color_tuple = (color_name, color_name2, color_name3) self._add_symbol( 'x3-%s' % '-'.join(map(str, color_tuple)), color_tuple, # drawing.rect( # insert=three_colored_flag_insert_points[0], # size=three_colored_flag_rect_size, # fill=fill_color, # ), # drawing.rect( # insert=three_colored_flag_insert_points[1], # size=three_colored_flag_rect_size, # fill=fill_color2, # ), # drawing.rect( # insert=three_colored_flag_insert_points[2], # size=three_colored_flag_rect_size, # fill=fill_color3, # ), drawing.rect( size=rect_size, fill=fill_color, ), drawing.polygon( points=three_colors_upper_points, fill=fill_color2, ), drawing.polygon( points=three_colors_lower_points, fill=fill_color3, ), ) # it's a little circle self._add_symbol('space', space_color, drawing.circle(r=cell_size / 10)) @property def pixel_side_width(self): """Horizontal clues side width in pixels""" return self.side_width * self.cell_size @property def pixel_header_height(self): """Vertical clues header height in pixels""" return self.header_height * self.cell_size @property def pixel_board_width(self): """The width of the main area in pixels""" return self.board.width * self.cell_size @property def pixel_board_height(self): """The height of the main area in pixels""" return self.board.height * self.cell_size @property def full_width(self): """Full width of the SVG board representation""" return self.pixel_side_width + self.pixel_board_width @property def full_height(self): """Full height of the SVG board representation""" return self.pixel_header_height + self.pixel_board_height def _color_from_name(self, color_name): return self.board.rgb_for_color_name(color_name) def block_svg(self, value, is_column, clue_number, block_number): """ Return the SVG element for the clue number (colored case included) """ # left to right, bottom to top block_number = -block_number shift = (0.85, -0.3) if is_column else (-0.3, 0.75) i, j = (clue_number, block_number) if is_column else (block_number, clue_number) if isinstance(value, (list, tuple)): # colored board value, color_id = value[:2] else: color_id = None block_color = None if color_id is not None: id_ = self.color_symbols[color_id] if is_column: color_box = (i, j - 1) else: color_box = (i - 1, j) # drawing.g(class_=id_) insert_point = (self.pixel_side_width + (color_box[0] * self.cell_size), self.pixel_header_height + (color_box[1] * self.cell_size)) block_color = (id_, insert_point) extra = dict() if color_id == Color.black().id_: extra['fill'] = 'white' if value == BlottedBlock: text_value = ClueCell.BLOTTED_SYMBOL else: text_value = str(value) block_text = self.drawing.text( text_value, insert=( self.pixel_side_width + (i + shift[0]) * self.cell_size, self.pixel_header_height + (j + shift[1]) * self.cell_size, ), **extra) return block_color, block_text def draw_header(self): drawing = self.drawing drawing.add( drawing.rect(size=(self.pixel_side_width, self.pixel_header_height), class_='nonogram-thumbnail')) drawing.add( drawing.rect(insert=(self.pixel_side_width, 0), size=(self.pixel_board_width, self.pixel_header_height), class_='nonogram-header')) header_group = drawing.g(class_='header-clues') for i, col_desc in enumerate(self.board.columns_descriptions): if self.board.column_solution_rate(i) == 1: x_pos = self.pixel_side_width + (i * self.cell_size) header_group.add( drawing.rect(insert=(x_pos, 0), size=(self.cell_size, self.pixel_header_height), class_='solved')) for j, desc_item in enumerate(reversed(col_desc)): color, text = self.block_svg(desc_item, True, i, j) # color first, text next (to write on color) if color: id_, insert_point = color icon = drawing.use( href='#' + id_, insert=insert_point, ) header_group.add(icon) header_group.add(text) drawing.add(header_group) def draw_side(self): drawing = self.drawing drawing.add( drawing.rect(insert=(0, self.pixel_header_height), size=(self.pixel_side_width, self.pixel_board_height), class_='nonogram-side')) side_group = drawing.g(class_='side-clues') for j, row_desc in enumerate(self.board.rows_descriptions): if self.board.row_solution_rate(j) == 1: y_pos = self.pixel_header_height + (j * self.cell_size) side_group.add( drawing.rect(insert=(0, y_pos), size=(self.pixel_side_width, self.cell_size), class_='solved')) for i, desc_item in enumerate(reversed(row_desc)): color, text = self.block_svg(desc_item, False, j, i) # color first, text next (to write on color) if color: id_, insert_point = color icon = drawing.use( href='#' + id_, insert=insert_point, ) side_group.add(icon) side_group.add(text) drawing.add(side_group) if self.board.is_solved_full: self._insert_solved_symbol() def _insert_solved_symbol(self): drawing = self.drawing check_icon_size = self.check_icon_size left_padding = (self.pixel_side_width - check_icon_size) / 2 top_padding = (self.pixel_header_height - check_icon_size) / 2 left_padding = max(left_padding, 0) top_padding = max(top_padding, 0) drawing.add(drawing.use('#check', insert=(left_padding, top_padding))) @classmethod def _color_code(cls, cell): if is_color_cell(cell): single_colors = two_powers(cell) if len(single_colors) > 3: # allow two and three colors # multiple colors return UNKNOWN return cell def draw_grid(self, cells=None): if cells is None: cells = self.board.cells drawing = self.drawing drawing.add( drawing.rect(insert=(self.pixel_side_width, self.pixel_header_height), size=(self.pixel_board_width, self.pixel_board_height), class_='nonogram-grid')) cell_groups = dict() for cell_value, id_ in iteritems(self.color_symbols): cell_groups[cell_value] = drawing.g(class_=id_) space_cell = SPACE_COLORED if self.is_colored else SPACE for j, row in enumerate(cells): for i, cell in enumerate(row): cell = self._color_code(cell) if cell == UNKNOWN: continue if cell == space_cell: insert_point = (self.pixel_side_width + (i + 0.5) * self.cell_size, self.pixel_header_height + (j + 0.5) * self.cell_size) else: # for boxes colored and black insert_point = (self.pixel_side_width + (i * self.cell_size), self.pixel_header_height + (j * self.cell_size)) id_ = self.color_symbols[cell] icon = drawing.use(href='#' + id_, insert=insert_point) cell_groups[cell].add(icon) # to get predictable order for cell_value, group in sorted(iteritems(cell_groups), key=lambda x: x[0]): drawing.add(group) # write grid on top of the colors self._insert_grid_lines() def _insert_grid_lines(self): drawing = self.drawing grid_lines = drawing.g(class_='grid-lines') for line in self._get_grid_lines(): grid_lines.add(line) drawing.add(grid_lines) def _get_grid_lines(self): drawing = self.drawing # draw horizontal lines for i in range(self.board.height + 1): extra = dict() if i % self.BOLD_EVERY == 0 or i == self.board.height: extra['class'] = 'bold' y_pos = self.pixel_header_height + (i * self.cell_size) yield drawing.line(start=(0, y_pos), end=(self.full_width, y_pos), **extra) # draw vertical lines for i in range(self.board.width + 1): extra = dict() if i % self.BOLD_EVERY == 0 or i == self.board.width: extra['class'] = 'bold' x_pos = self.pixel_side_width + (i * self.cell_size) yield drawing.line(start=(x_pos, 0), end=(x_pos, self.full_height), **extra) def render(self): self.drawing.write(self.stream) # self._print(self.drawing.tostring()) def draw(self, cells=None): self.drawing.elements = [] self.drawing.add(self.drawing.defs) super(SvgRenderer, self).draw(cells=cells)
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 legend_text = dwg.add( dwg.g(font_size=main_font_size, style="font-family: arial;")) legend_text.add(dwg.text('Phys', (x - 1500, y - 10), fill=color_phys)) legend_text.add(dwg.text('Scaled', (x - 1500, y + 20), fill=color_scaled)) v_lines = dwg.add(dwg.g(stroke_width=7.0, stroke=color_phys, fill='none')) v_lines_scaled = dwg.add( dwg.g(stroke_width=7.0, stroke=color_scaled, fill='none')) for loc in vertical_locs: v_lines.add(dwg.line(start=(loc, y + 15), end=(loc, 1300)))
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 legend_text = dwg.add(dwg.g(font_size=main_font_size, style="font-family: arial;")) legend_text.add(dwg.text('Phys', (x-1500, y-10), fill=color_phys)) legend_text.add(dwg.text('Scaled', (x-1500, y+20), fill=color_scaled)) v_lines = dwg.add(dwg.g(stroke_width=7.0, stroke=color_phys, fill='none')) v_lines_scaled = dwg.add(dwg.g(stroke_width=7.0, stroke=color_scaled, fill='none')) for loc in vertical_locs: v_lines.add(dwg.line(start=(loc, y+15), end=(loc, 1300))) extra_text = dwg.add(dwg.g(font_size=main_font_size - 3, style="font-family: arial;")) extra_text.add(dwg.text('fwd mode', (150, 50)))
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] def vertplace(j, col): val = col.iloc[j] if j > 0: prev = col.iloc[j-1]
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 draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: 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 = "font-size:2.5px; font-family:Arial" total_length_year_dict = self.poster.total_length_year_dict for year in self.poster.years.iter(): g_year = dr.g(id=f"year{year}") g.add(g_year) 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_str = utils.format_float(self.poster.m2u(year_length)) 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, ] ] km_or_mi = self.poster.u() g_year.add( dr.text( f"{year}", insert=offset.tuple(), fill=self.poster.colors["text"], alignment_baseline="hanging", style=year_style, )) g_year.add( dr.text( f"{year_length_str} {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): g_year.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 animate_index = 1 year_count = self.poster.year_tracks_date_count_dict[year] key_times = utils.make_key_times(year_count) 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 < distance2 color = self.color(self.poster.length_range_by_date, length, has_special) if length >= distance2: special_color = self.poster.colors.get( "special2") or self.poster.colors.get( "special") if special_color is not None: color = special_color str_length = utils.format_float( self.poster.m2u(length)) date_title = f"{date_title} {str_length} {km_or_mi}" # tricky for may cause animate error if animate_index < len(key_times) - 1: animate_index += 1 rect = dr.rect((rect_x, rect_y), dom, fill=color) if self.poster.with_animation: values = (";".join(["0"] * animate_index) + ";" + ";".join(["1"] * (len(key_times) - animate_index))) rect.add( svgwrite.animate.Animate( "opacity", dur=f"{self.poster.animation_time}s", values=values, keyTimes=";".join(key_times), repeatCount="1", )) rect.set_desc(title=date_title) g_year.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 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 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 self.poster.years: 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) 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, ] ] 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} Likes", insert=(offset.tuple()[0] + 160, offset.tuple()[1] + 4), 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] likes = sum([t["likes_count"] for t in tracks]) likes_gap = self.poster.special_likes["special_likes"] likes_gap2 = self.poster.special_likes[ "special_likes2"] if year < 2014: likes_gap = likes_gap / 10 likes_gap2 = likes_gap2 / 10 has_special = likes_gap < likes < likes_gap2 color = self.color(self.poster.length_range_by_date, likes, has_special) if likes >= likes_gap2: color = self.poster.colors.get( "special2") or self.poster.colors.get( "special") str_likes = str(likes) date_title = f"{date_title} {str_likes} Likes" 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_year(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY, year: int) -> None: 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(dr, g, center, radius_range) year_style = f"dominant-baseline: central; font-size:{min_size * 4.0 / 80.0}px; font-family:Arial;" month_style = f"font-size:{min_size * 3.0 / 80.0}px; font-family:Arial;" g.add( dr.text( f"{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 g.add( dr.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 = dr.path( d=("M", center.x + r3 * sin_a1, center.y - r3 * cos_a1), fill="none", stroke="none", ) path.push( f"a{r3},{r3} 0 0,1 {r3 * (sin_a3 - sin_a1)},{r3 * (cos_a1 - cos_a3)}" ) g.add(path) tpath = svgwrite.text.TextPath( path, self.poster.month_name(date.month), startOffset=(0.5 * r3 * (a3 - a1))) text = dr.text( "", fill=self.poster.colors["text"], text_anchor="middle", style=month_style, ) text.add(tpath) g.add(text) if text_date in self.poster.tracks_by_date: self._draw_circle_segment( dr, g, self.poster.tracks_by_date[text_date], a1, a2, radius_range, center, ) day += 1 date += datetime.timedelta(1)
def _create_group(self, drawing: Drawing, projection: np.ndarray, viewport: Viewport, group: Group): """ Render all the meshes contained in this group. The main consideration here is that we will consider the z-index of every object in this group. """ default_style = group.style or {} shaders = [ mesh.shader or (lambda face_index, winding: {}) for mesh in group.meshes ] annotators = [ mesh.annotator or (lambda face_index: None) for mesh in group.meshes ] # A combination of mesh and face indes mesh_faces: List[np.ndarray] = [] for i, mesh in enumerate(group.meshes): faces = mesh.faces # Extend each point to a vec4, then transform to clip space. faces = np.dstack([faces, np.ones(faces.shape[:2])]) faces = np.dot(faces, projection) # Reject trivially clipped polygons. xyz, w = faces[:, :, :3], faces[:, :, 3:] accepted = np.logical_and(np.greater(xyz, -w), np.less(xyz, +w)) accepted = np.all(accepted, 2) # vert is accepted if xyz are all inside accepted = np.any(accepted, 1) # face is accepted if any vert is inside degenerate = np.less_equal(w, 0)[:, :, 0] # vert is bad if its w <= 0 degenerate = np.any(degenerate, 1) # face is bad if any of its verts are bad accepted = np.logical_and(accepted, np.logical_not(degenerate)) faces = np.compress(accepted, faces, axis=0) # Apply perspective transformation. xyz, w = faces[:, :, :3], faces[:, :, 3:] faces = xyz / w mesh_faces.append(faces) # Sort faces from back to front. mesh_face_indices = self._sort_back_to_front(mesh_faces) # Apply viewport transform to X and Y. for faces in mesh_faces: faces[:, :, 0:1] = (1.0 + faces[:, :, 0:1]) * viewport.width / 2 faces[:, :, 1:2] = (1.0 - faces[:, :, 1:2]) * viewport.height / 2 faces[:, :, 0:1] += viewport.minx faces[:, :, 1:2] += viewport.miny # Compute the winding direction of each polygon. mesh_windings: List[np.ndarray] = [] for faces in mesh_faces: windings = np.zeros(faces.shape[0]) if faces.shape[1] >= 3: p0, p1, p2 = faces[:, 0, :], faces[:, 1, :], faces[:, 2, :] normals = np.cross(p2 - p0, p1 - p0) np.copyto(windings, normals[:, 2]) mesh_windings.append(windings) group_ = drawing.g(**default_style) text_group_ = drawing.g(**default_style) # Finally draw the group for mesh_index, face_index in mesh_face_indices: face = mesh_faces[mesh_index][face_index] style = shaders[mesh_index](face_index, mesh_windings[mesh_index][face_index]) if style is None: continue face = np.around(face[:, :2], self.precision) if len(face) == 1: group_.add( drawing.circle(face[0], style.pop("radius", 0.005), **style)) if len(face) == 2: group_.add(drawing.line(face[0], face[1], **style)) else: group_.add(drawing.polygon(face, **style)) annotation = annotators[mesh_index](face_index) if annotation is not None: centroid = face.mean(axis=0) text_group_.add(drawing.text(insert=centroid, **annotation)) return [group_, text_group_]