def draw_svg(self, drawing: svgwrite.Drawing, g): glane = drawing.g() glane.attribs["class"] = "lane" cp1 = self.control_points[0] cp2 = self.control_points[-1] rel = relative_pose(cp1.as_SE2(), cp2.as_SE2()) _, theta = geo.translation_angle_from_SE2(rel) delta = int(np.sign(theta)) fill = {-1: "#577094", 0: "#709457", 1: "#947057"}[delta] points = self.lane_profile(points_per_segment=10) p = drawing.polygon(points=points, fill=fill, fill_opacity=0.5) glane.add(p) center_points = self.center_line_points(points_per_segment=10) center_points = [ geo.translation_angle_from_SE2(_)[0] for _ in center_points ] p = drawing.polyline( points=center_points, stroke=fill, fill="none", stroke_dasharray="0.02", stroke_width=0.01, ) glane.add(p) g.add(glane) for x in self.control_points: q = x.asmatrix2d().m p1, _ = geo.translation_angle_from_SE2(q) delta_arrow = np.array([self.width / 4, 0]) p2 = SE2_apply_R2(q, delta_arrow) gp = drawing.g() gp.attribs["class"] = "control-point" l = drawing.line( start=p1.tolist(), end=p2.tolist(), stroke="black", stroke_width=self.width / 20.0, ) gp.add(l) c = drawing.circle( center=p1.tolist(), r=self.width / 8, fill="white", stroke="black", stroke_width=self.width / 20.0, ) gp.add(c) g.add(gp)
def draw_calendar(x: int, y: int, dwg: svgwrite.Drawing, lines: List[str]): shapes = dwg.add(dwg.g()) shapes.add(dwg.rect(insert=(x, y), size=(460 * px, 330 * px), fill='white', stroke='black', stroke_width=1)) offset = 35 for line in lines: date_text = dwg.add(dwg.g(font_size=36, font_family="Helvetica")) date_text.add(dwg.text(line, (10 + x, offset + y))) offset += 40
def render(self, dwg: Drawing) -> Group: g = dwg.g() leg = dwg.path(fill=self.leg_color) leg.push("M 0 0") leg.push("L 0 %f" % self.leg_length) leg.push("l %f 0" % self.thickness_foot) leg.push("L %f 0" % self.thickness_thigh) leg.push("Z") g.add(leg) boot_start = .7 boot_height = self.boot_height foot_length = self.foot_length boot = dwg.path(fill=self.foot_color) boot.push("M 0 %f" % (self.leg_length * boot_start)) boot.push("L 0 %f" % (self.leg_length + boot_height)) boot.push("l %f 0" % foot_length) boot.push( "a %f %f 0 0 0 %f %f" % (min(boot_height, abs(foot_length - self.thickness_foot)), boot_height, -min(boot_height, foot_length - self.thickness_foot), -boot_height)) boot.push("L %f %f" % (self.thickness_thigh - (self.thickness_thigh - self.thickness_foot) * boot_start + 1, self.leg_length * boot_start)) g.add(boot) return g
def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: """For each track, draw it on the poster.""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") cell_size, counts = utils.compute_grid(len(self.poster.tracks), size) if cell_size is None or counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] spacing_x = 0 if count_x <= 1 else (size.x - cell_size * count_x) / ( count_x - 1) spacing_y = 0 if count_y <= 1 else (size.y - cell_size * count_y) / ( count_y - 1) offset.x += (size.x - count_x * cell_size - (count_x - 1) * spacing_x) / 2 offset.y += (size.y - count_y * cell_size - (count_y - 1) * spacing_y) / 2 year_groups: typing.Dict[int, svgwrite.container.Group] = {} for (index, tr) in enumerate(self.poster.tracks): year = tr.start_time().year if year not in year_groups: g_year = dr.g(id=f"year{year}") g.add(g_year) year_groups[year] = g_year else: g_year = year_groups[year] p = XY(index % count_x, index // count_x) * XY( cell_size + spacing_x, cell_size + spacing_y) self._draw_track( dr, g_year, tr, 0.9 * XY(cell_size, cell_size), offset + 0.05 * XY(cell_size, cell_size) + p, )
def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: """Draw the circular Poster using distances broken down by time""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") if self.poster.length_range_by_date is None: return years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 4) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in self.poster.years.iter(): g_year = dr.g(id=f"year{year}") g.add(g_year) self._draw_year(dr, g_year, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
def _draw_cell(self, drw: Drawing, cell, stone_size): from_grid_to_edge = stone_size / 2 + self._margin x = (from_grid_to_edge + (cell.x - 1) * stone_size) y = (from_grid_to_edge + (cell.y - 1) * stone_size) node = cell.node cell = drw.add(drw.g(id=f'cell{cell.x}-{cell.y}')) if node.stone is not None: self._put_stone(cell, x, y, node.stone, stone_size) if node.stone is None: half_size = 3 * stone_size / 8 cell.add( Rect( ((x - half_size) * self.unit, (y - half_size) * self.unit), (2 * half_size * self.unit, 2 * half_size * self.unit), fill="white")) fill = "white" if node.stone == Stone.Black else "black" if isinstance(node.marker, game.Label): self._put_label(cell, x, y, node.marker.label, fill, stone_size) elif isinstance(node.marker, game.Square): self._put_square(cell, fill, stone_size, x, y) elif isinstance(node.marker, game.Circle): self._put_circle(cell, x, y, fill, stone_size) elif isinstance(node.marker, game.Cross): self._put_cross(cell, x, y, fill, stone_size) elif isinstance(node.marker, game.Triangle): self._put_triangle(cell, x, y, fill, stone_size)
def plot(self, x, y, scale, **extra): assert isinstance(scale, (int, float)) assert isinstance(x, (int, float)) assert isinstance(y, (int, float)) svg = Drawing() g = svg.g() p1 = [x, y] p2 = [x + max(self.ticks) * scale, y] g.add(svg.line(start=p1, end=p2, stroke="black", stroke_width=1)) for i, tick in enumerate(self.ticks): p1 = [x + tick * scale, y - 5] p2 = [x + tick * scale, y + 0.5] g.add(svg.line(start=p1, end=p2, stroke="black", stroke_width=1)) g.add( svg.text(text="%s" % self.labels[i], insert=[x + tick * scale, y + 10], font_size=8, font_family="Arial")) return g
def _draw_tracks(self, d: svgwrite.Drawing, size: XY, offset: XY) -> None: assert self.tracks_drawer g = d.g(id="tracks") d.add(g) self.tracks_drawer.draw(d, g, size, offset)
def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: """Iterate through the Poster's years, creating a calendar for each.""" if self.poster.tracks is None: raise PosterError("No tracks to draw.") years = self.poster.years.count() _, counts = utils.compute_grid(years, size) if counts is None: raise PosterError("Unable to compute grid.") count_x, count_y = counts[0], counts[1] x, y = 0, 0 cell_size = size * XY(1 / count_x, 1 / count_y) margin = XY(4, 8) if count_x <= 1: margin.x = 0 if count_y <= 1: margin.y = 0 sub_size = cell_size - 2 * margin for year in self.poster.years.iter(): g_year = dr.g(id=f"year{year}") g.add(g_year) self._draw(dr, g_year, sub_size, offset + margin + cell_size * XY(x, y), year) x += 1 if x >= count_x: x = 0 y += 1
def draw(self, dr: svgwrite.Drawing, g: svgwrite.container.Group, size: XY, offset: XY) -> None: """Draw the heatmap based on tracks.""" bbox = self._determine_bbox() year_groups: typing.Dict[int, svgwrite.container.Group] = {} for tr in self.poster.tracks: year = tr.start_time().year if year not in year_groups: g_year = dr.g(id=f"year{year}") g.add(g_year) year_groups[year] = g_year else: g_year = year_groups[year] color = self.color(self.poster.length_range, tr.length(), tr.special) for line in utils.project(bbox, size, offset, tr.polylines): for opacity, width in [(0.1, 5.0), (0.2, 2.0), (1.0, 0.3)]: g_year.add( dr.polyline( points=line, stroke=color, stroke_opacity=opacity, fill="none", stroke_width=width, stroke_linejoin="round", stroke_linecap="round", ))
def _draw_header(self, d: svgwrite.Drawing) -> None: g = d.g(id="header") d.add(g) text_color = self.colors["text"] title_style = "font-size:12px; font-family:Arial; font-weight:bold;" assert self._title is not None g.add(d.text(self._title, insert=(10, 20), fill=text_color, style=title_style))
def draw_small(x: int, y: int, dwg: svgwrite.Drawing, time: str, weather: WeatherForecast, icons_location: str): shapes = dwg.add(dwg.g()) shapes.add(dwg.rect(insert=(x, y), size=(220 * px, 145 * px), fill='white', stroke='black', stroke_width=1)) date_text = dwg.add(dwg.g(font_size=35, font_family="Helvetica")) date_text.add(dwg.text(time, (10 + x, 35 + y))) paragraph = dwg.add(dwg.g(font_size=55)) paragraph.add(dwg.text(f"{weather.temperature}°", (x + 135, y + 90), text_anchor="middle")) location = os.path.abspath(f"{icons_location}/{weather.summary.value}.svg") image = dwg.add( dwg.image(href=location, insert=(10 + x, 45 + y), size=(55 * px, 55 * px))) pressure_text = dwg.add(dwg.g(font_size=36, font_family="Helvetica")) pressure_text.add(dwg.text(f"{int(weather.air_pressure)} hPa", (10 + x, 130 + y)))
def render(self, d: Drawing) -> Group: g = d.g() r = d.image(href=self.href) r["xlink:href"] = self.href r["width"] = "%dpx" % self.width r["height"] = "%dpx" % self.height g.add(r) return g
def render(self, dwg: Drawing) -> Group: g = dwg.g() p = dwg.path(stroke=self.color, fill_opacity=0, stroke_width=3) p.push("M %f %f" % (-self.width / 2, 0)) p.push("Q %f %f %f %f" % (0, self.width * self.intensity, self.width / 2, 0)) g.add(p) return g
def render(self, dwg: Drawing) -> Group: g = dwg.g() p = dwg.path(fill="black", stroke_width=0) p.push("M %f %f" % (-self.width / 2, 0)) p.push("Q %f %f %f %f" % (0, self.width * self.intensity, self.width / 2, 0)) p.push("Z") g.add(p) return g
def render(self, d: Drawing) -> Group: g = d.g() href = "data:image/jpeg;base64,%s" % base64.b64encode( self.data).decode() r = d.image(href=href) r["xlink:href"] = href r["width"] = "%dpx" % self.width r["height"] = "%dpx" % self.height g.add(r) return g
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 draw_big(x: int, y: int, dwg: svgwrite.Drawing, weather: WeatherForecast, air_quality: AirQuality, icons_location: str): shapes = dwg.add(dwg.g(id='shapes')) shapes.add(dwg.rect(insert=(x, y), size=(360 * px, 300 * px), fill='white', stroke='black', stroke_width=1)) paragraph = dwg.add(dwg.g(font_size=120)) paragraph.add(dwg.text(f"{weather.temperature}°", (x + 240, y + 110), text_anchor="middle")) location = os.path.abspath(f"{icons_location}/{weather.summary.value}.svg") image = dwg.add( dwg.image(href=location, insert=(x + 5, y + 5), size=(120 * px, 120 * px))) feels_like_text = dwg.add(dwg.g(font_size=36, font_family="Helvetica")) feels_like_text.add(dwg.text(f"Air quality: {air_quality}", (x + 10, y + 165))) wind_text = dwg.add(dwg.g(font_size=36, font_family="Helvetica")) wind_text.add(dwg.text(f"{weather.wind_name} {weather.wind_mps} m/s", (x + 10, y + 205), textLength=340)) precipitation_text = dwg.add(dwg.g(font_size=36, font_family="Helvetica")) precipitation_text.add(dwg.text(f"{weather.precip_name} {weather.precip_mm} mm/h", (x + 10, y + 245))) pressure_text = dwg.add(dwg.g(font_size=36, font_family="Helvetica")) pressure_text.add(dwg.text(f"{int(weather.air_pressure)} hPa", (x + 10, y + 285)))
def display_svg(): dwg = Drawing() hlines = dwg.add(dwg.g(id="hlines", stroke="green")) for y in range(20): hlines.add( dwg.line(start=(2 * cm, (2 + y) * cm), end=(18 * cm, (2 + y) * cm))) vlines = dwg.add(dwg.g(id="vline", stroke="blue")) for x in range(17): vlines.add( dwg.line(start=((2 + x) * cm, 2 * cm), end=((2 + x) * cm, 21 * cm))) shapes = dwg.add(dwg.g(id="shapes", fill="red")) # set presentation attributes at object creation as SVG-Attributes circle = dwg.circle(center=(15 * cm, 8 * cm), r="2.5cm", stroke="blue", stroke_width=3) circle["class"] = "class1 class2" shapes.add(circle) # override the 'fill' attribute of the parent group 'shapes' shapes.add( dwg.rect( insert=(5 * cm, 5 * cm), size=(45 * mm, 45 * mm), fill="blue", stroke="red", stroke_width=3, )) # or set presentation attributes by helper functions of the Presentation-Mixin ellipse = shapes.add( dwg.ellipse(center=(10 * cm, 15 * cm), r=("5cm", "10mm"))) ellipse.fill("green", opacity=0.5).stroke("black", width=5).dasharray([20, 20]) return Response(dwg.tostring(), mimetype="image/svg+xml")
def render(self, dwg: Drawing) -> Group: r = dwg.rect(insert=(0, 0), size=(self.width, self.height), fill=self.color) g = dwg.g() g.add(r) star_count = int(self.width * self.height * .001) for i in range(star_count): s = dwg.circle(center=(self.prng.randint(0, self.width), self.prng.randint(0, self.height)), r=max(2, self.prng.gauss(1, 1)), fill="white") g.add(s) return g
def plot(self, shape="rect", scale=0, strand=True, **extra): svg = Drawing() g = svg.g() previous = None for feature in self.features: if not previous: previous = feature continue x1, y1 = previous.position x2, y2 = feature.position y1 += (2 + previous.strand) * previous.height + previous.height / 2 y2 += (feature.strand - 1) * feature.height - previous.height / 2 p1 = [x1, y1] p2 = [x1 + previous.length * scale, y1] p3 = [x2 + feature.length * scale, y2] p4 = [x2, y2] if shape == "rect": g.add( svg.polygon(points=[p1, p2, p3, p4], fill=self.color, fill_opacity=0.6)) if shape == "line": g.add( svg.polyline([ ((p1[0] + p2[0]) / 2, y1 - previous.height / 2), ((p1[0] + p2[0]) / 2, y1), ((p3[0] + p4[0]) / 2, y2), ((p3[0] + p4[0]) / 2, y2 + feature.height / 2), ], fill_opacity=0, stroke=self.color, stroke_width=2)) previous = feature return g
def render(self, dwg: Drawing) -> Group: width, height, scale, x_translate, y_translate = self.random_configuration() image_url = "/assets/%s" % self.img if self.local_paths: image_url = "file://%s" % os.path.join(ASSET_DIR, self.img) g = dwg.g() r = dwg.image(href=self.img) r["xlink:href"] = image_url r["width"] = "%dpx" % width r["height"] = "%dpx" % height r.scale(scale) r.translate(- x_translate, - y_translate) g.add(r) return g
def render(self, dwg: Drawing) -> Group: g = dwg.g() eye = dwg.circle(center=(0, 0), r=self.radius, fill="white", stroke_width=0) g.add(eye) pupil_radius = self.radius * self.pupil_radius pupil_rho = min(self.radius * self.pupil_rho, self.radius * (1 - self.pupil_radius)) pupil_coords = (pupil_rho * math.cos(self.pupil_phi), pupil_rho * math.sin(self.pupil_phi)) pupil = dwg.circle(center=pupil_coords, r=pupil_radius, fill="black", stroke_width=0) g.add(pupil) reflection_radius = pupil_radius * .2 reflection_rho = pupil_radius * .6 reflection_phi = 1.75 * math.pi reflection_coords = (pupil_coords[0] + reflection_rho * math.cos(reflection_phi), pupil_coords[1] + reflection_rho * math.sin(reflection_phi)) reflection = dwg.circle(center=reflection_coords, r=reflection_radius, fill="white") g.add(reflection) return g
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())
# 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]
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
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 _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_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 render(self, dwg: Drawing) -> Group: g = dwg.g() head = self.head.render(dwg) socket_relative_width = 1.2 socket_radius = (self.head_size * socket_relative_width, self.head_size*0.3) socket_relative_height = .3 socket_left = (-self.head_size * socket_relative_width, self.head_size * .5) socket_left_bottom = (socket_left[0], socket_left[1] + self.head_size * socket_relative_height) socket_right: Tuple[float, float] = (self.head_size * socket_relative_width, self.head_size * .5) socket_right_bottom: Tuple[float, float] = (socket_right[0], socket_right[1] + self.head_size * socket_relative_height) size_factor = self.head_size / 50.0 arm_length = 50 * size_factor arm_params = { "arm_length": arm_length, "arm_color": self.body_color, "hand_color": helper.colors.lighten_hex(self.body_color, 2), "thickness_shoulder": 30 * size_factor } arm_params.update(self.arm_params) for i in range(self.arm_count): left_arm = ArmWithHand(**arm_params) # type: ignore left_arm_g = left_arm.render(dwg) left_arm_x = socket_right_bottom[0] - left_arm.thickness_shoulder / 2 - (socket_right_bottom[0] - self.head_size * self.body_fatness) / (self.head_size * self.body_height) * i * left_arm.thickness_shoulder * 1.2 left_arm_g.translate(left_arm_x, socket_right_bottom[1] + left_arm.thickness_shoulder / 2 + i * left_arm.thickness_shoulder * .8) left_arm_g.rotate(self.body_left_arm_angle / (math.pi) * 180 + (i * 20)) g.add(left_arm_g) right_arm = ArmWithHand(reverse_shadow=True, **arm_params) # type: ignore right_arm_g = right_arm.render(dwg) right_arm_x = socket_left_bottom[0] + right_arm.thickness_shoulder / 2 + (-self.head_size * self.body_fatness - socket_left_bottom[0]) / (self.head_size * self.body_height) * i * right_arm.thickness_shoulder * 1.2 right_arm_g.translate(right_arm_x, socket_left_bottom[1] + right_arm.thickness_shoulder / 2 + i * right_arm.thickness_shoulder * .8) right_arm_g.rotate(-self.body_right_arm_angle / (math.pi) * 180 - (i * 20)) right_arm_g.scale(-1, 1) g.add(right_arm_g) leg_thickness_thigh = self.body_fatness * self.head_size leg_thickness_foot = leg_thickness_thigh * .7 leg_length = self.head_size * 1 boot_height = leg_length * .5 foot_length = leg_length left_leg = LegWithFoot(leg_length=leg_length, # type: ignore leg_color=self.body_color, thickness_thigh=leg_thickness_thigh, thickness_foot=leg_thickness_foot, foot_color=helper.colors.lighten_hex(self.body_color, 2), boot_height=boot_height, foot_length=foot_length, **self.leg_params) left_leg_g = left_leg.render(dwg) left_leg_g.translate(0, self.head_size * self.body_height) left_leg_g.rotate(-20) g.add(left_leg_g) right_leg = LegWithFoot(leg_length=leg_length, # type: ignore leg_color=self.body_color, thickness_thigh=leg_thickness_thigh, thickness_foot=leg_thickness_foot, foot_color=helper.colors.lighten_hex(self.body_color, 2), boot_height=boot_height, foot_length=foot_length, **self.leg_params) right_leg_g = right_leg.render(dwg) right_leg_g.translate(0, self.head_size * self.body_height) right_leg_g.rotate(20) right_leg_g.scale(-1, 1) g.add(right_leg_g) body = dwg.path(fill=self.body_color) body.push("M %f %f" % (socket_right_bottom[0], socket_right_bottom[1])) body.push("L %f %f" % (self.head_size * self.body_fatness, self.head_size * self.body_height)) body.push("L %f %f" % (self.head_size * (self.body_fatness - .2), self.head_size * (self.body_height + .2))) body.push("L %f %f" % (-self.head_size * (self.body_fatness - .2), self.head_size * (self.body_height + .2))) body.push("L %f %f" % (-self.head_size * self.body_fatness, self.head_size * self.body_height)) body.push("L %f %f" % (socket_left_bottom[0], socket_left_bottom[1])) g.add(body) socket_background_color = helper.colors.darken_hex(self.socket_color) socket_background = dwg.ellipse(fill=socket_background_color, center=(0, self.head_size * .5), r=socket_radius) socket_foreground = dwg.path(fill=self.socket_color) socket_foreground.push("M %f %f" % socket_left) socket_foreground.push("A %f %f 0 0 0 %f %f" % (socket_radius[0], socket_radius[1], socket_right[0], socket_right[1])) socket_foreground.push("l 0 %f" % (self.head_size * .3)) socket_foreground.push("A %f %f 0 0 1 %f %f" % (socket_radius[0], socket_radius[1], - self.head_size * socket_relative_width, self.head_size * .8)) g.add(socket_background) g.add(head) g.add(socket_foreground) dome = dwg.path(fill="white", fill_opacity=.3) dome.push("M %f %f" % socket_left) dome.push("C %f %f %f %f %f %f" % (-self.head_size * (socket_relative_width + 1), -self.head_size * 3, self.head_size * (socket_relative_width + 1), -self.head_size * 3, socket_right[0], socket_right[1])) dome.push("A %f %f 0 0 1 %f %f" % (socket_radius[0], socket_radius[1], socket_left[0], socket_left[1])) refl_mask = dwg.defs.add(dwg.mask((self.head_size * -1.5, self.head_size * -2.5), (self.head_size * 3, self.head_size * 5), id="%s-dome-refl-mask" % self.id)) refl_mask.add(dwg.rect((self.head_size * -1.5, self.head_size * -2.5), (self.head_size * 3, self.head_size * 5), fill="white")) refl_mask.add(dwg.circle((self.head_size * .3, -self.head_size * .25), r=self.head_size * 1.75, fill="black")) dome_reflection = dwg.path(fill="white", fill_opacity=.3, mask="url(#%s-dome-refl-mask)" % self.id) dome_reflection.push("M %f %f" % socket_left) dome_reflection.push("C %f %f %f %f %f %f" % (-self.head_size * (socket_relative_width + 1), -self.head_size * 3, self.head_size * (socket_relative_width + 1), -self.head_size * 3, socket_right[0], socket_right[1])) dome_reflection.push("A %f %f 0 0 1 %f %f" % (socket_radius[0], socket_radius[1], socket_left[0], socket_left[1])) dome_reflection.scale(.9) g.add(dome) g.add(dome_reflection) return g
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 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 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}" rect = dr.rect((rect_x, rect_y), dom, fill=color) 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