def draw_single_pentagon(self): pentagon = self.new_pentagon() dwg = Drawing("{}/single_pentagon.svg".format(self.output_folder), profile='tiny') dwg.add( dwg.path( **{ 'd': pentagon.d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) dwg.viewbox(self.pent_x, self.pent_y, self.pent_width, self.pent_height) dwg.save()
def create_svg_document_with_light(elements, size, viewbox=None, background_color="white", background_opacity=1.0): """Create the full SVG document, with a lighting filter. Resources: - https://www.w3.org/TR/SVG11/filters.html#LightSourceDefinitions - https://svgwrite.readthedocs.io/en/master/classes/filters.html - http://www.svgbasics.com/filters2.html - https://css-tricks.com/look-svg-light-source-filters/ :param viewbox: (minx, miny, width, height) """ # TODO work in progress # TODO have a look at how threejs is converted to SVG: # https://github.com/mrdoob/three.js/blob/master/examples/jsm/renderers/SVGRenderer.js dwg = Drawing("ase.svg", profile="full", size=size) light_filter = dwg.defs.add(Filter(size=("100%", "100%"))) diffuse_lighting = light_filter.feDiffuseLighting(size=size, surfaceScale=10, diffuseConstant=1, kernelUnitLength=1, color="white") diffuse_lighting.fePointLight(source=(size[0], 0, 1000)) light_filter.feComposite(operator="arithmetic", k1=1) root = Group(id="root", filter=light_filter.get_funciri()) dwg.add(root) # if Color(background_color).web != "white": # apparently the best way, see: https://stackoverflow.com/a/11293812/5033292 root.add( shapes.Rect(size=size, fill=background_color, fill_opacity=background_opacity)) for element in elements: root.add(element) if viewbox: dwg.viewbox(*viewbox) return dwg
def 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_path_clip(self): path_filename = "{}/path_clip_{}.svg".format( self.output_folder, basename(self.filename).replace(".svg", "")) dwg = Drawing(path_filename) image_bbox = calc_overall_bbox(self.tile_paths) dx = self.pent_x - min(image_bbox[0], image_bbox[1]) dy = self.pent_y - min(image_bbox[2], image_bbox[3]) dwg.add( dwg.path( **{ "d": self.new_pentagon().d(), "fill": "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) neg_transform = "translate({}, {})".format(-dx, -dy) transform = "translate({}, {})".format(dx, dy) clip_path = dwg.defs.add( dwg.clipPath(id="pent_path", transform=neg_transform)) clip_path.add(dwg.path(d=self.new_pentagon().d())) group = dwg.add( dwg.g(clip_path="url(#pent_path)", transform=transform, id="clippedpath")) for i, path in enumerate(self.tile_paths): group.add( dwg.path(d=path.d(), style=self.tile_attributes[i].get('style'), id=self.tile_attributes[i]['id'])) dwg.add(dwg.use("#clippedpath", transform="transform(100, 100)")) dwg.viewbox(self.pent_x, self.pent_y, self.pent_width, self.pent_height) dwg.save() xml = xml.dom.minidom.parse(path_filename) open(path_filename, "w").write(xml.toprettyxml())
def create_svg_document(elements, size, viewbox=None, background_color="white", background_opacity=1.0): """Create the full SVG document. :param viewbox: (minx, miny, width, height) """ dwg = Drawing("ase.svg", profile="tiny", size=size) root = Group(id="root") dwg.add(root) # if Color(background_color).web != "white": # apparently the best way, see: https://stackoverflow.com/a/11293812/5033292 root.add( shapes.Rect(size=size, fill=background_color, fill_opacity=background_opacity)) for element in elements: root.add(element) if viewbox: dwg.viewbox(*viewbox) return dwg
def flatten_scene(pScene): lNode = pScene.GetRootNode() if not lNode: return for i in range(lNode.GetChildCount()): lChildNode = lNode.GetChild(i) if lChildNode.GetNodeAttribute() is None: continue lAttributeType = (lChildNode.GetNodeAttribute().GetAttributeType()) if lAttributeType != FbxNodeAttribute.eMesh: continue lMesh = lChildNode.GetNodeAttribute() projected_points = {} control_points = lMesh.GetControlPoints() start_point = 0 poly_paths = [] for polygon_num in range(lMesh.GetPolygonCount()): corners = [] for corner in range(3): corners.append(lMesh.GetPolygonVertex(polygon_num, corner)) # first, check if any of the control points are already projected flattened = [] for j, corner in enumerate(corners): if corner in projected_points: flattened.append(projected_points[corner]) continue target_corner = corners[j - 1] current_vec = control_points[corner] target_vec = control_points[target_corner] angle = acos( current_vec.DotProduct(target_vec) / (current_vec.Length() * target_vec.Length())) length = current_vec.Distance(target_vec) # find where the last point was. If it doesn't exist, use the start point start_corner = projected_points[target_corner] \ if target_corner in projected_points else start_point flattened_corner = start_corner + length * (cos(angle) + 1j * sin(angle)) projected_points[corner] = flattened_corner start_point = flattened_corner flattened.append(flattened_corner) poly_paths.append( Path(*[ Line(start=flattened[j], end=flattened[j - 1]) for j in range(3) ])) dwg = Drawing("mesh{}.svg".format(i), profile='tiny') for poly_path in poly_paths: dwg.add( dwg.path( **{ 'd': poly_path.d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) bbox = calc_overall_bbox(poly_paths) width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2]) dwg.viewbox(min(bbox[0], bbox[1]), min(bbox[2], bbox[3]), width, height) dwg.save()
def visualize(self, trajectory, output_file): drawing = Drawing() # Определение параметров образов состояний аппарата machine_view_length, machine_view_width = self.__machine_view_size coordinates_scaling = machine_view_length / self.__machine_length machine_diameter = \ ((machine_view_length * 2.0) ** 2.0 + machine_view_width ** 2.0) \ ** 0.5 machine_radius = machine_diameter / 2.0 # Создание последовательности записываемых состояний аппарата def generate_states_sequence(): spawn_time = 0.0 for trajectory_time, state in trajectory: if trajectory_time >= spawn_time: spawn_time += self.__time_interval yield state states_sequence = generate_states_sequence() # Запись последовательности состояний аппарата is_view_box_initialized = False view_box_minimal_x, view_box_minimal_y = 0.0, 0.0 view_box_maximal_x, view_box_maximal_y = 0.0, 0.0 for state in states_sequence: # Создание образа состояния аппарата state_view_angle = - state.coordinates[2] / math.pi * 180.0 state_view_center = \ state.coordinates[0] * coordinates_scaling, \ - state.coordinates[1] * coordinates_scaling state_view_position = \ state_view_center[0], \ state_view_center[1] - machine_view_width / 2.0 state_view = \ Rect( insert = state_view_position, size = self.__machine_view_size, fill = rgb(255, 255, 255), stroke = rgb(0, 0, 0), stroke_width = 1 ) state_view.rotate( state_view_angle, center = state_view_center ) # Добавление образа состояния аппарата к образу траектории drawing.add(state_view) if is_view_box_initialized: view_box_minimal_x, view_box_minimal_y = \ min(state_view_center[0], view_box_minimal_x), \ min(state_view_center[1], view_box_minimal_y) view_box_maximal_x, view_box_maximal_y = \ max(state_view_center[0], view_box_maximal_x), \ max(state_view_center[1], view_box_maximal_y) else: is_view_box_initialized = True view_box_minimal_x, view_box_minimal_y = \ state_view_center[0], \ state_view_center[1] view_box_maximal_x, view_box_maximal_y = \ state_view_center[0], \ state_view_center[1] # Настройка отображения образа траектории drawing.viewbox( minx = view_box_minimal_x - machine_radius, miny = view_box_minimal_y - machine_radius, width = view_box_maximal_x - view_box_minimal_x + machine_diameter, height = view_box_maximal_y - view_box_minimal_y + machine_diameter ) # Запись образа траектории в файл try: drawing.write(output_file) except: raise Exception() #!!!!! Генерировать хорошие исключения
def flatten_shape(i, all_paths, merge_paths): dwg = Drawing("merge_output%s.svg" % i, profile='tiny') def draw_line(start, end, offset=0.0): start += offset end += offset dwg.add( dwg.line(start=(start.real, start.imag), end=(end.real, end.imag), stroke_width=4, stroke=rgb(255, 0, 0))) dwg.add( dwg.path( **{ 'd': all_paths[i].d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) dwg.add( dwg.path( **{ 'd': merge_paths[i].d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(255, 0, 0) })) bbox = calc_overall_bbox(all_paths[i]) width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2]) margin = 40 lower = min(bbox[2], bbox[3]) + height + margin left = min(bbox[0], bbox[1]) + margin def draw_marker(loc, col=rgb(255, 0, 0), offset=(left, lower)): dwg.add( dwg.circle(center=(loc.real + offset[0], loc.imag + offset[1]), r=4, fill=col)) max_axis = max(width, height) num_lines = 10 points = [merge_paths[i].point(j / num_lines) for j in range(num_lines)] + [merge_paths[i].point(1.0)] angles = [ asin((points[j + 1].imag - points[j].imag) / abs(points[j + 1] - points[j])) for j in range(num_lines) ] ends = [max_axis * (sin(angle) + cos(angle) * 1j) for angle in angles] intersection_clips = [] for j, end in enumerate(ends): end_point = end + points[j] intersections = other_paths[i].intersect( Line(start=points[j], end=end_point)) for intersection in intersections[0]: intersection_point = intersection[1].point(intersection[2]) target = merge_paths[i].length() * ( 1 - j / num_lines) + abs(intersection_point - points[j]) * 1j intersection_clips.append( PathClip(index=other_paths[i].index(intersection[1]), t=intersection[2], target=target)) if j % 10 == 0: draw_line(points[j], intersection_point) draw_marker(intersection_point, rgb(0, 255, 0), (0, 0)) break # make the flexed points by chopping the chunks of the other paths out, then # translating and rotating them such that their end points line up with the diff lines def transform_side(sides, targets, angle_offset=0): def angle(point1, point2): diff = point1 - point2 if diff.real == 0: return 90.0 return atan(diff.imag / diff.real) * 180.0 / pi # change this so that it has two targets transformed_side = Path(*sides) source_angle = angle(transformed_side.end, transformed_side.start) - \ angle(targets[0], targets[1]) transformed_side = transformed_side.rotated(-source_angle + angle_offset) source = transformed_side.end if angle_offset == 0 else transformed_side.start diff = targets[1] - source transformed_side = transformed_side.translated(diff) draw_marker(targets[0], rgb(0, 200, 200)) draw_marker(targets[1], rgb(0, 255, 255)) transformed_diff = abs(transformed_side.start - transformed_side.end) targets_diff = abs(targets[0] - targets[1]) if transformed_diff < targets_diff: transformed_side.insert( 0, Line(start=targets[0], end=transformed_side.start)) elif transformed_diff > targets_diff: # pop elements off until the transformed diff is smaller while transformed_diff > targets_diff: transformed_side.pop(0) transformed_diff = abs(transformed_side.start - transformed_side.end) print("path", transformed_side) print("path is longer", transformed_diff - targets_diff) return transformed_side start_index = 0 curr_t = 0 flexed_path = [] t_resolution = 0.01 if intersection_clips[0].index > intersection_clips[-1].index or \ (intersection_clips[0].index == intersection_clips[-1].index and intersection_clips[0].t > intersection_clips[-1].t): intersection_clips.reverse() # add the end of the shape to the intersection clips intersection_clips.append( PathClip(index=len(other_paths[i]) - 1, t=1.0, target=merge_paths[i].length())) last_target = 0 for clip in intersection_clips: sides = [] print("boundaries", start_index, clip.index, curr_t, clip.t) upper_t = clip.t if start_index == clip.index else 1.0 while start_index <= clip.index and curr_t < upper_t: curr_seg = other_paths[i][start_index] while curr_t < upper_t: max_t = curr_t + t_resolution if curr_t + t_resolution < clip.t else clip.t sides.append( Line(start=curr_seg.point(curr_t), end=curr_seg.point(max_t))) curr_t += t_resolution curr_t = upper_t if start_index != clip.index: curr_t = 0.0 if upper_t == 1.0: start_index += 1 upper_t = clip.t if start_index == clip.index else 1.0 if len(sides) != 0: flexed_path.append( transform_side(sides, [last_target, clip.target])) last_target = clip.target straight_path = [Line(start=0, end=merge_paths[i].length())] for p in flexed_path: p = p.translated(left + lower * 1j) dwg.add( dwg.path(d=p.d(), fill="none", stroke_width=4, stroke=rgb(255, 0, 0))) transformed_path = flexed_path + straight_path transformed_path = Path(*transformed_path).translated(left + lower * 1j) dwg.add( dwg.path(d=transformed_path.d(), fill="none", stroke_width=4, stroke=rgb(0, 0, 0))) bbox = calc_overall_bbox(list(all_paths[i]) + list(transformed_path)) width, height = abs(bbox[1] - bbox[0]), abs(bbox[3] - bbox[2]) dwg.viewbox(min(bbox[0], bbox[1]), min(bbox[2], bbox[3]), width, height) dwg.save() return flexed_path
upper_sizes = [max(bbox[0], bbox[1]), abs(bbox[3] - bbox[2])] transform = "scale(1, {})".format(-1 if i == 0 else 1) group = dwg.add(dwg.g(transform=transform)) for path in path_list: path = path.translated(-min(bbox[2], bbox[3]) * 1j) group.add( dwg.path( **{ 'd': path.d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) bbox = calc_overall_bbox(flexed_paths[1]) dwg.viewbox( min(bbox[0], bbox[1]), -upper_sizes[1], abs(min(bbox[0], bbox[1]) - max(bbox[0], bbox[1], upper_sizes[0])), abs(bbox[3] - bbox[2]) + upper_sizes[1]) dwg.save() # render the shapes selected dwg = Drawing("merge_output.svg", profile='tiny') for path in all_paths: dwg.add( dwg.path( **{ 'd': path.d(), 'fill': "none", 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) dwg.add( dwg.path(
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 reset(self): self.stack = [] self.points = [] dwg = Drawing("canvas.svg", (self.width, self.height)) dwg.viewbox(0, 0, self.width, self.height) self.stack.append(dwg)
end_index = \ [i for i, l in enumerate(lines) if l.find('EMC') >= 0 and i > start_index][0] shape = "\n".join(lines[start_index:end_index]) paths += parse_shape(shape, page_num, gstates) # all paths paths = sorted(list(set(paths))) vals = {'fill': 'none', 'stroke': rgb(0, 0, 0), 'stroke-width': 4} output_filename = "all_paths.svg" dwg = Drawing(output_filename, profile='tiny') overall_bbox = calc_overall_bbox(paths) print(overall_bbox) for path in paths: vals['d'] = path dwg.add(dwg.path(**vals)) dwg.viewbox(overall_bbox[0], overall_bbox[2], abs(overall_bbox[1] - overall_bbox[0]), abs(overall_bbox[3] - overall_bbox[2])) dwg.save() # make svgs of all paths for i, ds in enumerate(paths): output_filename = "path%s.svg" % i dwg = Drawing(output_filename, profile='tiny') if isinstance(ds, list): for d in ds: vals['d'] = d, dwg.add(dwg.path(**vals)) else: vals['d'] = ds shape = parse_path(ds) bbox = calc_overall_bbox(shape._segments)
def draw_pattern(self): self.output_filename = "{}/snake_tiling_m_{}_{}.svg".format( self.output_folder, self.dx, self.dy) dwg = Drawing(self.output_filename) # add background panel background_clippath = dwg.rect(insert=(self.pattern_viewbox[0], self.pattern_viewbox[1]), size=('100%', '100%')) background_panel = dwg.rect(insert=(self.pattern_viewbox[0], self.pattern_viewbox[1]), size=('101%', '101%'), fill='#3072a2') clip_path = dwg.defs.add(dwg.clipPath(id="background_panel")) clip_path.add(background_clippath) clipped_drawing = dwg.add( dwg.g(clip_path="url(#background_panel)", id="clippedpath")) clipped_drawing.add(background_panel) current_color = 0 def add_pentagon(group, transform, current_color, draw_pent=True): pent_group = group.add( dwg.g(transform=format_transform(*transform))) if draw_pent: pent_group.add( dwg.path( **{ 'd': self.new_pentagon().d(), 'fill': self.colors[current_color % len(self.colors)], 'stroke-width': 4, 'stroke': rgb(0, 0, 0) })) return pent_group for y in range(self.num_down): transform = "translate({}, {})".format(0, self.rep_spacing * y) dgroup = clipped_drawing.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 diff = dx + self.column_offset.imag * 1j else: diff = int(x / 2) * self.rep_spacing for i in range(4): snake_bbox = calc_overall_bbox(self.tile_paths) snake_width, snake_height = abs(snake_bbox[0] - snake_bbox[1]), \ abs(snake_bbox[2] - snake_bbox[3]) pent_group = add_pentagon( dgroup, (self.transforms[i][0], self.transforms[i][1] + diff), current_color, draw_pent=False) for i, path in enumerate(self.tile_paths): stransform = 'translate({},{})'.format( snake_width * self.dx, snake_height * self.dy) pent_group.add( dwg.path( **{ 'd': path.d(), 'style': self.tile_attributes[i].get( 'style'), 'id': self.tile_attributes[i]['id'], 'transform': stransform })) current_color += 1 dwg.viewbox(*self.pattern_viewbox) dwg.save()
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_node(node, store, size=None): """Construct a SVG description for a workflow node. Args: node (NodeDef) store (dict of uid, def): elements definitions size (int, int): size of drawing in pixels Returns: (str) - SVG description of workflow node """ pfs = port_font_size # node size pr = port_radius pspace = pr * 9 nw = compute_node_width(node, node['name'], pspace) nh = label_font_size + 2 * pr + 2 * pfs + 2 + (2 * node_padding) # draw if size is None: size = (600, 600) paper = Drawing("workflow_node.svg", size, id="repr") lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="in_port") lg.add_stop_color(0, color='#3333ff') lg.add_stop_color(1, color='#2222ff') paper.defs.add(lg) lg = paper.linearGradient((0.5, 0), (0.5, 1.), id="out_port") lg.add_stop_color(0, color='#ffff33') lg.add_stop_color(1, color='#9a9a00') paper.defs.add(lg) # body g = paper.add(paper.g()) # background lg = paper.linearGradient((0.5, 0), (0.5, 1.)) lg.add_stop_color(0, color='#8c8cff') lg.add_stop_color(1, color='#c8c8c8') paper.defs.add(lg) bg = paper.rect((-nw / 2, -nh / 2), (nw, nh), rx=node_padding, ry=node_padding, stroke_width=1) bg.stroke('#808080') bg.fill(lg) g.add(bg) # label style = ('font-size: %dpx; font-family: %s; ' 'text-anchor: middle' % (label_font_size, label_font)) frag = paper.tspan(node['name'], dy=[label_font_size // 3]) label = paper.text("", style=style, fill='#000000') label.add(frag) g.add(label) # ports port_style = ('font-size: %dpx; ' % pfs + 'font-family: %s; ' % label_font) onstyle = port_style + 'text-anchor: end' instyle = port_style + 'text-anchor: start' istyle = port_style + 'text-anchor: middle' nb = len(node['inputs']) py = -nh / 2 for i, pdef in enumerate(node['inputs']): px = i * pspace - pspace * (nb - 1) / 2 pg = g.add(paper.g()) pg.translate(px, py) idef = store.get(pdef['interface'], None) if idef is not None and 'url' in idef: link = pg.add(paper.a(href=idef['url'], target='_top')) else: link = pg port = paper.circle((0, 0), pr, stroke='#000000', stroke_width=1) port.fill("url(#in_port)") link.add(port) # port name frag = paper.tspan(pdef['name'], dy=[-2 * pr]) label = paper.text("", style=instyle, fill='#000000') label.rotate(-45) label.add(frag) pg.add(label) # port interface if idef is None: itxt = pdef['interface'] else: itxt = idef['name'] if len(itxt) > 10: itxt = itxt[:7] + "..." frag = paper.tspan(itxt, dy=[pr + pfs]) label = paper.text("", style=istyle, fill='#000000') label.add(frag) link.add(label) nb = len(node['outputs']) py = nh / 2 for i, pdef in enumerate(node['outputs']): px = i * pspace - pspace * (nb - 1) / 2 pg = g.add(paper.g()) pg.translate(px, py) idef = store.get(pdef['interface'], None) if idef is not None and 'url' in idef: link = pg.add(paper.a(href=idef['url'], target='_top')) else: link = pg port = paper.circle((0, 0), pr, stroke='#000000', stroke_width=1) port.fill("url(#out_port)") link.add(port) # port name frag = paper.tspan(pdef['name'], dy=[2 * pr + pfs // 2]) label = paper.text("", style=onstyle, fill='#000000') label.rotate(-45) label.add(frag) pg.add(label) # port interface if idef is None: itxt = pdef['interface'] else: itxt = idef['name'] if len(itxt) > 10: itxt = itxt[:7] + "..." frag = paper.tspan(itxt, dy=[-pr - 2]) label = paper.text("", style=istyle, fill='#000000') label.add(frag) link.add(label) # reformat whole drawing to fit screen xmin = -nw / 2 - draw_padding / 10. xmax = +nw / 2 + draw_padding / 10. if len(node['inputs']) == 0: inames_extend = 0 else: inames = [(len(pdef['name']), pdef['name']) for pdef in node['inputs']] inames_extend = string_size(sorted(inames)[-1][1], pfs) * 0.7 + pfs ymin = -nh / 2 - pr - inames_extend - draw_padding / 10. if len(node['outputs']) == 0: onames_extend = 0 else: onames = [(len(pdef['name']), pdef['name']) for pdef in node['outputs']] onames_extend = string_size(sorted(onames)[-1][1], pfs) * 0.7 + pfs ymax = +nh / 2 + pr + onames_extend + draw_padding / 10. w = float(size[0]) h = float(size[1]) ratio = max((xmax - xmin) / w, (ymax - ymin) / h) xsize = ratio * w ysize = ratio * h bb = (xmin * xsize / (xmax - xmin), ymin * ysize / (ymax - ymin), xsize, ysize) paper.viewbox(*bb) return paper.tostring(), bb
class DrawTemplateSVG(object): def __init__(self, tmax, grid, darea=DrawArea, symbols=SymbolAssets, m3d=Matrix3D): self._off = (2.2, 2.5) size = 100 self._size = (size, size * 1.9) self._matrix3d = m3d(tmax[0], self._size, self._off, grid) self._area = darea(self._off, self._size, tmax[0], tmax[1], grid).area() self.dwg = Drawing('graph.svg', size=self._area, id="graph") viewp = self.fixOneLineRoot(grid) self.dwg.viewbox(*viewp) self._symbols = symbols(self.dwg) self.setup() # When you have only one or two items, we need to fix the grid area def fixOneLineRoot(self, grid): key_min = min(grid.keys()) rootSize = len(grid[key_min]) size = len(grid) area = [0, 0, *self._area] if key_min <= 1 and rootSize <= 2: ajy = self._size[0] * 1.8 ajx = self._size[0] * 0.5 area[1] = 0 - ajx area[3] = self._area[1] + ajy if size <= 1: area[0] = 0 - ajx return area def setup(self): self.setup_brightness() self.setup_marker() def setup_brightness(self): self._symbols.brightness() self._symbols.brightness("darker", 0.4) def setup_marker(self): s = self._size[0] / 8 opts = {'insert': (152, 3), 'size': (s, s)} self._symbols.asset_marker('markers.arrow', opts) def boundary_box(self, pos, node): opts = {'id': node.get('_id')} symbol = self._symbols.asset('boundaries_box.front', 'default boundaries', (pos[0], pos[1]), self._size, opts) self.add(symbol) def draw_label(self, pos, node): text = HelperDrawLabel(self._size, self._matrix3d) \ .label_by_node(pos, node) symbol = self._symbols.text(*text) self.add(symbol) def draw_app(self, item): cad1 = [item[x] for x in range(2)] node = item[3] pos = self._matrix3d.rotateNodeXY(item)() self.draw_root(item) self.draw_grid_size(cad1, item[2]) self.grid_box(pos) self.draw_execute(pos, node) self.boundary_box(pos, node) self.draw_label(pos, node) self.draw_tooltips(node) def draw_execute(self, pos, node): hDrawApp = HelperDrawApplication(self._size) hDrawApp.execute(pos, node) pSymb = hDrawApp.get_apps() for symb in pSymb: symbol = self._symbols.asset(*symb) self.add(symbol) def draw_tooltips(self, node): _id = "tool-" + node.get('_id') g = self._symbols.create_group(_id) hDrawTooltips = HelperDrawTooltips(self._size, self._off) hDrawTooltips.execute(node) ltxt = hDrawTooltips.get_text() symbol = self._symbols.multiline(ltxt, (0, 0)) g.add(symbol) def grid_box(self, pos, opts={'fill-opacity': '0.4'}): symbol = self._symbols.asset('grid.base', 'default', pos, self._size, opts) self.add(symbol) def draw_grid_size(self, cad1, size): cad2 = (cad1[0], cad1[1] + size - 1) points = HelperDrawBasePolyline(self._size, self._matrix3d) \ .create_polyline_by_pos(cad1, cad2) symbol = self._symbols.polyline(points, { 'fill': '#ccc', 'fill-opacity': 0.2 }) self.add(symbol) def draw_root(self, item): root = item[3].get('root') if root: nitem = (item[0] - 1, item[1], item[2], item[3]) pos = self._matrix3d.rotateNodeXY(nitem)() symbol = self._symbols.asset('grid.entry', 'default', pos, self._size) self.draw_connect(nitem, item) self.add(symbol) def add(self, symbol): self.dwg.add(symbol) def save(self): return self.dwg.tostring() def draw_connect(self, node1, node2, details=None): id = "%s-%s" % (node1[0], node2[0]) g = self.dwg.g(id=id) d = HelperDrawConnector(self._size, self._off, self._matrix3d).connect(node1, node2) symbol = self._symbols.conn(d) g.add(symbol) cls = " conn-%s" % node1[3].get('_id') symbol = self._symbols.conn_holder(d, cls) g.add(symbol) symbol = self._symbols.text(details, (0, 0), {'display': 'none'}) g.add(symbol) self.add(g)