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 export_workflow(workflow, store, size=None): """Construct a SVG description for a workflow. Args: workflow (WorkflowDef) store (dict of uid, def): elements definitions size (int, int): size of drawing in pixels Returns: (str) - SVG description of workflow """ # check that each node has a position for node in workflow['nodes']: if 'x' not in node or 'y' not in node: raise UserWarning("need to position workflow first") if size is None: size = (600, 600) # draw paper = Drawing("workflow.svg", size, id="repr") lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="bg_loaded") lg.add_stop_color(0, color='#8c8cff') lg.add_stop_color(1, color='#c8c8c8') paper.defs.add(lg) lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="bg_failed") lg.add_stop_color(0, color='#ff8cff') lg.add_stop_color(1, color='#c8c8c8') paper.defs.add(lg) 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) for i, link in enumerate(workflow['links']): draw_link(paper, workflow, store, link, i) bbs = [] for i, node in enumerate(workflow['nodes']): bb = draw_node(paper, workflow, store, node, i) bbs.append(bb) # reformat whole drawing to fit screen xmin = min(bb[0] for bb in bbs) - draw_padding xmax = max(bb[2] for bb in bbs) + draw_padding ymin = min(bb[1] for bb in bbs) - draw_padding ymax = max(bb[3] for bb in bbs) + draw_padding w = float(size[0]) h = float(size[1]) xratio = (xmax - xmin) / w yratio = (ymax - ymin) / h if xratio > yratio: xsize = int(xratio * w) ysize = int(xratio * h) ymin -= (ysize - (ymax - ymin)) / 2 else: xsize = int(yratio * w) ysize = int(yratio * h) xmin -= (xsize - (xmax - xmin)) / 2 paper.viewbox(xmin, ymin, xsize, ysize) return paper.tostring(), (xmin, ymin, xsize, ysize)
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 plot(self, svg: Drawing) -> None: recolor: Optional[str] = None if isinstance(self.color, list): linear_gradient: LinearGradient = svg.linearGradient( self.map_((0, self.max_y)), self.map_((0, 1)), gradientUnits="userSpaceOnUse", ) for index, color in enumerate(self.color): linear_gradient.add_stop_color( index / (len(self.color) - 1), color.hex ) gradient: LinearGradient = svg.defs.add(linear_gradient) recolor = gradient.get_funciri() if isinstance(self.color, Color): recolor = self.color.hex svg.add( svg.rect( insert=(0, 0), size=("100%", "100%"), rx=None, ry=None, fill=self.background_color.hex, ) ) self.draw_grid(svg) last_text_y = 0 for xs, ys, color, title in self.data: if recolor: color = recolor assert len(xs) == len(ys) xs_second: list[float] = [ (x - self.min_x).total_seconds() for x in xs ] points = [] for index, x in enumerate(xs_second): y = ys[index] mapped: np.ndarray = map_array( np.array((x, y)), np.array((self.min_x_second, self.max_y)), np.array((self.max_x_second, self.min_y)), self.canvas.workspace[0], self.canvas.workspace[1], ) points.append(mapped) previous_point: Optional[np.ndarray] = None for point in points: if previous_point is not None: line: Line = svg.line( (previous_point[0], previous_point[1]), (point[0], point[1]), stroke=self.background_color.hex, stroke_width=6, ) svg.add(line) line: Line = svg.line( (previous_point[0], previous_point[1]), (point[0], point[1]), stroke=color, stroke_width=2, ) svg.add(line) previous_point = point for point in points: svg.add( svg.circle( (point[0], point[1]), 5.5, fill=self.background_color.hex, ) ) svg.add(svg.circle((point[0], point[1]), 3.5, fill=color)) title: str text_y = max(last_text_y + 20, point[1] + 6) self.text(svg, (point[0] + 15, text_y), title.upper(), color) last_text_y = text_y with Path(svg.filename).open("w+") as output_file: svg.write(output_file)