def createSVG(filename, tool, points): # Generate the paths allPoints = list() paths = list() t = Template(PATH_TEMPLATE) for point in points: allPoints.extend(point) path = Path() lx, ly = point[0] pc = 0 for p in point[1:]: l = mkLine(lx, ly, p[0], p[1]) if l is not None: path.append(l) pc = pc + 1 lx = p[0] ly = p[1] path.closed = True paths.append(t.safe_substitute({ 'strokeWidth': tool, 'path': path.d() })) print "Generated path with %d points." % pc # Build the final output v = { 'xmin': min([ x for x, y in allPoints ]) - (tool / 2), 'ymin': min([ y for x, y in allPoints ]) - (tool / 2), 'xmax': max([ x for x, y in allPoints ]) + (tool / 2), 'ymax': max([ y for x, y in allPoints ]) + (tool / 2) } v['width'] = abs(v['xmax'] - v['xmin']) v['height'] = abs(v['ymax'] - v['ymin']) v['path'] = "\n".join(paths) out = Template(SVG_TEMPLATE).substitute(v).strip() # And write the file with open(filename, "w") as svg: svg.write(out)
def breakIntoLines(segment): #I may need to check if it is a move command and do nothing... #for now I am just going to hardcode this but it is possible that I will make it dynamic in the future numberOfSegments = 20 baseSegmentStep = 1 / (numberOfSegments) segmentStep = 1 / (numberOfSegments) k = 0 linesOnlySegment = Path() initialPoint = segment.point(0.0) while (k <= numberOfSegments): currentPoint = segment.point(segmentStep) if (k == 0): returnedLine = createLineSegment(currentPoint, initialPoint) else: returnedLine = createLineSegment(currentPoint, lastPoint) linesOnlySegment.append(returnedLine) lastPoint = segment.point(segmentStep) k = k + 1 segmentStep = baseSegmentStep * k return linesOnlySegment
def __init__(self, path): Path.__init__(self) elements.SymbolicMixin.__init__(self) # build path from segments for seg in path._segments: clss = 'Symbolic' + seg.__class__.__name__ self._segments.append(getattr(elements, clss)(seg)) self.__symbolic_setup()
def test_Path_close_adds_a_close_path_command_to_the_path(): path = Path((0, 1)).v_line(-1.2, relative=True) path.h_line(2.5, relative=True) path.close() assert str(path) == '<path d="M 0 1 v -1.2 h 2.5 Z"/>\n' path = Path((0, 2)).h_line(2.5, relative=True).close() assert str(path) == '<path d="M 0 2 h 2.5 Z"/>\n'
def test_get_path_not_starting_at_0(self): test_path = Path(Line(start=(0 + 0j), end=(2 + 2j)), Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j))) expected_result = Trajectory.discretize( Path(Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j)), Line(start=(0 + 0j), end=(2 + 2j))), 1) traj = Trajectory() traj.paths.append(test_path) result = traj.get_path(0, 1, 1) self.assertEqual(result, expected_result)
def createPathObject(numberOfFiles): k = 0 while (k < numberOfFiles): parsedPath = Path() individualSegmentCommand = [] individualSegmentCollection = [] f = open("onlyCurves" + str(k) + ".txt", "r") pathToFormString = str(f.readline()) f.close() listOfSegments = re.split("M|m|L|l|H|h|V|v|C|c|S|s|Q|q|T|t|A|a|Z|z", pathToFormString) svgCommands = re.findall("M|m|L|l|H|h|V|v|C|c|S|s|Q|q|T|t|A|a|Z|z", pathToFormString) for x in range(len(svgCommands)): individualSegmentCommand.append( (svgCommands[x] + listOfSegments[x + 1]).strip()) individualSegmentCollection.append(individualSegmentCommand) # for y in range(len(individualSegmentCollection[0])): # # parsedPath.append(individualSegmentCollection[0][y]) # parsedPath = Path(individualSegmentCollection[0]) f = open("onlyCurves" + str(k) + ".txt", "w") f.write(str(individualSegmentCollection[0])) f.close() k = k + 1
def add_highlight_lines(self, lines): for line in lines: new_path_node = self.dom.createElement("path") new_path_node.setAttribute('d', Path(line).d()) new_path_node.setAttribute('fill', 'none') new_path_node.setAttribute('stroke', '#FF0000') new_path_node.setAttribute('stroke-width', '1') self.svg_node.appendChild(new_path_node)
def length(self): """ Compute overall length of path. Returns ------- length : numpy.float Length of path """ return np.float(Path.length(self))
def test_path_is_homogeneously_sampled(self): test_path = Path(Line(start=(0 + 0j), end=(5 + 0j))) expected_result = [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] traj = Trajectory() traj.paths.append(test_path) result = traj.get_path(0, 0, 1) for (x1, y1), (x2, y2) in zip(result, expected_result): self.assertAlmostEqual(x1, x2, delta=0.01) self.assertAlmostEqual(y1, y2, delta=0.01)
def test_get_normalized_path(self): traj = Trajectory() test_path = Path(Line(start=(0 + 0j), end=(2 + 2j)), Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j))) expected_path = Trajectory.scale( Trajectory.discretize( Path(Line(start=(0 + 0j), end=(2 + 2j)), Line(start=(2 + 2j), end=(7 + 1j)), Line(start=(7 + 1j), end=(0 + 0j))), 1), 1 / 7.0, 1 / 2.0) traj.paths.append(test_path) normalized_trajectory = traj.get_normalized_path(0) self.assertGreater(len(normalized_trajectory), 3) for (x1, y1), (x2, y2) in zip(normalized_trajectory, expected_path): self.assertAlmostEqual(x1, x2, delta=0.01) self.assertAlmostEqual(y1, y2, delta=0.01)
def path_to_polygon(path): polygon = [] global max_x, max_y, min_x, min_y new_path = Path() for segment in parse_path(path): new_path.append(Line(segment.start, segment.end)) new_path.closed = True raw_path = new_path.d() # search for compound path bits and remove them path_bits = re.findall('M.+?[ZM]', raw_path) if len(path_bits) > 0: raw_path = path_bits[0] raw_path_size = len(re.findall(',', raw_path)) for bit in path_bits: bit_size = len(re.findall(',', bit)) if bit_size > raw_path_size: raw_path = bit raw_path_size = bit_size # convert to simple list of nodes nodes = re.findall('[ML]\s*(\d+\.*\d*,\d+\.*\d*)\s*', raw_path) for n in nodes: coords = n.split(',') if max_x < int(coords[0]): max_x = int(coords[0]) if max_y < int(coords[1]): max_y = int(coords[1]) if min_x > int(coords[0]): min_x = int(coords[0]) if min_y > int(coords[1]): min_y = int(coords[1]) polygon.append([int(coords[0]), int(coords[1])]) polygon.pop() return polygon
def _get_closed_subpaths(path): ''' Generates all closed subpaths raises error if open subpaths exist. Credit to @tatarize: https://github.com/regebro/svg.path/issues/54#issuecomment-570101018 ''' segments = None for p in path: if isinstance(p, Move): if segments is not None: raise RuntimeError("Only closed shapes accepted.") segments = [] segments.append(p) if isinstance(p, Close): yield Path(*segments) segments = None
def createSVG(filename, tool, points): # Generate the paths allPoints = list() paths = list() t = Template(PATH_TEMPLATE) for point in points: allPoints.extend(point) path = Path() lx, ly = point[0] pc = 0 for p in point[1:]: l = mkLine(lx, ly, p[0], p[1]) if l is not None: path.append(l) pc = pc + 1 lx = p[0] ly = p[1] path.closed = True paths.append(t.safe_substitute({ 'strokeWidth': tool, 'path': path.d() })) print "Generated path with %d points." % pc # Build the final output v = { 'xmin': min([x for x, y in allPoints]) - (tool / 2), 'ymin': min([y for x, y in allPoints]) - (tool / 2), 'xmax': max([x for x, y in allPoints]) + (tool / 2), 'ymax': max([y for x, y in allPoints]) + (tool / 2) } v['width'] = abs(v['xmax'] - v['xmin']) v['height'] = abs(v['ymax'] - v['ymin']) v['path'] = "\n".join(paths) out = Template(SVG_TEMPLATE).substitute(v).strip() # And write the file with open(filename, "w") as svg: svg.write(out)
''' SVG2 = ''' </g> </svg> ''' svgpath1 = '''<path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' all_paths = [] print(SVG1) for p in paths: new_path = Path() path = parse_path(p.attrib["d"]) all_paths.append(svgpath1 + path.d() + svgpath2) mul = sys.argv[2] count = 0 MAX = 43330 for x in range(int(mul)): if (count >= MAX): break for p in all_paths: if (count >= MAX): break print(p)
def get_paths(edges, nodes, node_type, extra_nodes, pos, label, label_size, pathway, cofactors = None, min_path_length = 10, max_bend = 40, prevent_overlap = True, direction_default = 'v', reverse_cofactor_direction = []): """ Get svg-paths for the metabolic map. INPUT: 'edges': a list of edges; i.e. (reaction node, species node) tuples. 'nodes': a list of nodes. 'node_type': a dictionary with node types; keys are nodes and values are the type of node, being either 'reaction' or 'species'. 'extra_nodes': a dictionary with nodes that are not representative of reactions or species, but help with drawing svg-paths by giving extra points through which the path must be drawn. 'pos': a dictionary with coordinates of the nodes; keys are nodes and values are (x,y) tuples. 'label': a dictionary with labels; keys are nodes and values are metabolite or reaction names. 'label_size': a dictionary with node label size; keys are nodes and values are [width, height] tuples 'pathway': a dictionary with pathways; keys are nodes and values are pathways (if 'cycle' is in the pathway name and the node is on a circle of nodes in the same pathway, paths will be drawn as arcs instead of bezier curves). 'cofactors' a dictionary with cofactors; keys are reactions and values are species. 'min_path_length': minimal length of paths. 'max_bend': curvature of the paths. 'prevent_overlap': prevent overlap of paths and labels. 'direction_default': default direction of paths at reaction nodes. 'reverse_cofactors': list of reactions in which cofactors must be placed in opposite order OUTPUT: A dictionary with the svg-paths; keys are edges and values are svg paths (svg path 'd' attribute) """ if direction_default: direction_default = direction_default[0].lower() s_nodes = set() r_nodes = [] for n in nodes: if node_type[n] == 'reaction': r_nodes.append(n) else: s_nodes.add(n) # get circular paths, exclude edges with circular paths from further editing arc_paths = {} # svg arcs # find cycles cyclic_pathways = set([pw for pw in pathway.values() if 'cycle' in pw.lower()]) for pw in cyclic_pathways: circle_nodes = [n for n in nodes if pathway[n] == pw] # get nodes that are placed on a circle circle_nodes = get_nodes_on_circle(pos, circle_nodes) # get center coordinates of circle cx = sum([pos[n][0] for n in circle_nodes])/len(circle_nodes) cy = sum([pos[n][1] for n in circle_nodes])/len(circle_nodes) # find closest node pairs for each source node sourcenodes = [n for n in circle_nodes if node_type[n]=='reaction'] potential_edges = [] for sn in sourcenodes: # get distances for all nodes on circle dist = [eucdist((pos[sn][0], pos[sn][1]), (pos[n][0], pos[n][1])) for n in circle_nodes] # sort nodes based on distance sorted_n = [n for (dst,n) in sorted(zip(dist, circle_nodes))] # get edges with closest nodes potential_edges += [(sn, tn) for tn in sorted_n[1:]][:2] # get edges that are part of circle circle_edges = [e for e in edges if e in potential_edges] # get arc paths for e in circle_edges: x0, y0 = pos[e[0]][0], pos[e[0]][1] # reaction node x, y = pos[e[1]][0], pos[e[1]][1] # species node w, h = label_size[e[1]] r = math.sqrt((x - cx)**2 + (y - cy)**2) # get the 2 intersections of the circle with the rectangular label 'bounding box' intersections = intersect_circ_rect(cx, cy, r, x, y, w, h) # get the intersection closest to reaction node, this will be the path end dist = [eucdist((x0, y0), intrsc) for intrsc in intersections] intrsc = intersections[dist.index(min(dist))] # determine sweep-flag for the svg arc angle0 = np.angle(complex((x0-cx),(y0-cy))) angle1 = np.angle(complex((intrsc[0]-cx),(intrsc[1]-cy))) if angle1-angle0 < 0: swp = 0 # anti-clockwise else: swp = 1 # clockwise arc_paths[e] = arc_path(x0, y0, r, swp, *intrsc) edges.remove(e) v_nodes={} for r in r_nodes: v_nodes[r] = [] # voter nodes for reaction direction p_nodes={} # path nodes coordinates (by edge) p_nodes_flat = [] # list of path nodes coordinates direction = {} for e in edges: p_nodes[e]=[pos[e[0]]] + extra_nodes[e] + [pos[e[1]]] p_nodes_flat += p_nodes[e] v_nodes[e[0]].append(p_nodes[e][1]) if 'cycle' in pathway[e[0]].lower() or not direction_default: direction[e]=['h']*len(p_nodes[e]) else: direction[e]=[direction_default]*len(p_nodes[e]) cof_adj = {}.fromkeys(edges, 0) if cofactors: for e in edges: if len(cofactors[e[0]])>0: cof_adj[e] = 1.5*min_path_length # reserve space for cofactors else: cofactors = {}.fromkeys(r_nodes, {}) path_end = {} for e in edges: path_end[e[1]] = {} for e in edges: # determine curve direction of path at reaction node if 'cycle' in pathway[e[0]].lower() or not direction_default: direction[e][0] = path_node_direction(pos[e[0]], v_nodes[e[0]]) # get cofactor positions and paths if e[0] in reverse_cofactor_direction: dst = (min_path_length, -1.5*min_path_length) else: dst = (min_path_length, 1.5*min_path_length) cofactors[e[0]] = get_cofactors_layout(pos[e[0]], cofactors[e[0]], direction[e][0], label, label_size, dist=dst) for s in cofactors[e[0]]: s_nodes.add(str(s)+str(e[0])) label_size[str(s)+str(e[0])] = label_size[s] pos[str(s)+str(e[0])] = cofactors[e[0]][s]['pos'] # determine curve direction of path at helper nodes for i in range(1, len(p_nodes[e])-1): n = p_nodes[e][i] direction[e][i] = path_node_direction(n, [p_nodes[e][i-1], p_nodes[e][i+1]]) # determine curve direction of path at species node dx = pos[e[1]][0] - p_nodes[e][-2][0] dy = pos[e[1]][1] - p_nodes[e][-2][1] direction[e][-1] = path_direction_end(dx, dy, direction[e][-1], label_size[e[1]], min_length = min_path_length) if not 'cycle' in pathway[e[0]].lower() and direction_default == 'v' and direction[e][-1] == 'h' and (p_nodes[e][-2][0], pos[e[1]][1]) in p_nodes_flat: direction[e][-1] = 'v' # determine endpoint of path at species node p_nodes[e][-1] = path_position_end(pos[e[1]], dx, dy, direction[e][-1], label_size[e[1]]) # collect path ends for duplicates adjustment path_end[e[1]][e[0]] = p_nodes[e][-1] # adjust horizontal path ends (no more than 1 on each side of species label) max_hz = 1 # maximum number of horizontal path ends for s in path_end: left = [] right = [] for r in path_end[s]: if path_end[s][r][1] == pos[s][1]: if path_end[s][r][0] < pos[s][0]: left.append(r) else: right.append(r) for rns in [left, right]: if len(rns) > max_hz: rns.sort(key = lambda r: abs(pos[s][1] - pos[r][1])) # sort on y-distance to reaction node for r in rns[1:]: e = (r,s) dx = pos[s][0] - p_nodes[e][-2][0] dy = pos[s][1] - p_nodes[e][-2][1] direction[e][-1] = 'v' p_nodes[e][-1] = path_position_end(pos[e[1]], dx, dy, direction[e][-1], label_size[e[1]]) path_end[s][r] = p_nodes[e][-1] # adjust duplicate path ends for s in path_end: path_end[s] = adjust_duplicate_path_ends(s, path_end[s], pos[s], p_nodes) for e in edges: p_nodes[e][-1] = path_end[e[1]][e[0]] # get path segments path_segs = {} for e in edges: path_segs[e]=[] for i in range(len(p_nodes[e])-1): # get list of path segments segs = get_path_segments( start = complex(*p_nodes[e][i]), end = complex(*p_nodes[e][i+1]), start_direction = direction[e][i], end_direction = direction[e][i+1], max_bend = max_bend, adjust = [cof_adj[e],0]) path_segs[e].append(segs) if not prevent_overlap: # return paths if no overlap prevention svg_paths = arc_paths for e in edges: p = [] for segs in path_segs[e]: p+=segs svg_paths[e] = Path(*p).d() for r in cofactors: for s in cofactors[r]: svg_paths[(r,s)] = cofactors[r][s]['path'] return svg_paths, pos ############################################### ########## adjust path/label overlap ########## # change path segment basic shape if overlapping with labels print 'checking path/label overlap...' for e in edges: for i in range(len(path_segs[e])): seg = path_segs[e][i] p = Path(*seg) # get 100 (x + yj) points on the path points = [p.point(t) for t in [k/100. for k in range(101)]] for n in s_nodes.difference({e[1]}): # check if any points are inside species labels if overlapping(points, pos[n], label_size[n]): # change j shape to s shape print 'changing path {} shape to prevent {} label overlap...'.format(e, n) direction[e][i+1] = direction[e][i] break # determine new path ends dx = pos[e[1]][0] - p_nodes[e][-2][0] dy = pos[e[1]][1] - p_nodes[e][-2][1] p_nodes[e][-1] = path_position_end(pos[e[1]], dx, dy, direction[e][-1], label_size[e[1]]) # collect path ends for duplicates adjustment path_end[e[1]][e[0]] = p_nodes[e][-1] # adjust horizontal path ends max_hz =1 # (no more than 1 path end on each side of species label) for s in path_end: left = [] right = [] for r in path_end[s]: if path_end[s][r][1] == pos[s][1]: if path_end[s][r][0] < pos[s][0]: left.append(r) else: right.append(r) for rns in [left, right]: if len(rns) > max_hz: rns.sort(key = lambda r: abs(pos[s][1] - pos[r][1])) # sort on y-distance to reaction node for r in rns[1:]: e = (r,s) dx = pos[s][0] - p_nodes[e][-2][0] dy = pos[s][1] - p_nodes[e][-2][1] direction[e][-1] = 'v' p_nodes[e][-1] = path_position_end(pos[e[1]], dx, dy, direction[e][-1], label_size[e[1]]) path_end[s][r] = p_nodes[e][-1] # adjust duplicate path ends for s in path_end: path_end[s] = adjust_duplicate_path_ends(s, path_end[s], pos[s], p_nodes) for e in edges: p_nodes[e][-1] = path_end[e[1]][e[0]] # get new path segments vv_shaped=[] hh_shaped=[] path_segs = {} for e in edges: path_segs[e]=[] for i in range(len(p_nodes[e])-1): # get list of path segments adj = [cof_adj[e],0] start = complex(*p_nodes[e][i]) end = complex(*p_nodes[e][i+1]) segs = get_path_segments( start, end, start_direction = direction[e][i], end_direction = direction[e][i+1], max_bend = max_bend, adjust = adj) path_segs[e].append(segs) # collect information for finding and adjusting path/path overlap if direction[e][i] == direction[e][i+1] == 'v': dx = end.real - start.real dy = end.imag - start.imag y = start.imag + 0.5*dy + adj[0]*dy/abs(dy) info = {'y':y, 'dx':dx, 'dy':dy, 'start':start, 'end':end, 'e,i':(e,i)} vv_shaped.append(info) if direction[e][i] == direction[e][i+1] == 'h': dx = end.real - start.real dy = end.imag - start.imag x = start.real + 0.5*dx + adj[0]*dx/abs(dx) info = {'x':x, 'dx':dx, 'dy':dy, 'start':start, 'end':end, 'e,i':(e,i)} hh_shaped.append(info) print 'adjusting path/path overlap' vv_shaped.sort(key = lambda k:k['y']) # sort by midpoint y-coordinate for y, info in groupby(vv_shaped, key = lambda k:k['y']): # determine overlap for all path segments with same midpoint y-coordinate segs_to_adjust = [] for c in combinations(list(info), 2): min_x1 = min(c[0]['start'].real, c[0]['end'].real) max_x1 = max(c[0]['start'].real, c[0]['end'].real) min_x2 = min(c[1]['start'].real, c[1]['end'].real) max_x2 = max(c[1]['start'].real, c[1]['end'].real) if max(min_x1, min_x2) < min(max_x1, max_x2): # paths have overlap! if len(segs_to_adjust) == 0: segs_to_adjust.append(list(c)) else: for lst in segs_to_adjust: if c[0] in lst and not c[1] in lst: lst.append(c[1]) break elif c[1] in lst and not c[0] in lst: lst.append(c[0]) break else: # if neither c[0] or c[1] in any lst: segs_to_adjust.append(list(c)) for lst in segs_to_adjust: lst.sort(key = lambda k:k['start'].imag) lst.sort(key = lambda k:k['start'].real*-k['dx']/abs(k['dx'])) l = len(lst) mp_adj = [(l*-5 +5) + i*10 for i in range(l)] # e.g. [-10, 0, 10] for l = 3 for n in range(l): dy = lst[n]['dy'] e, i = lst[n]['e,i'] start = lst[n]['start'] end = lst[n]['end'] adj = [cof_adj[e], 0, 0.5*abs(dy) -cof_adj[e] + mp_adj[n]] segs = get_path_segments(start, end, 'v', 'v', max_bend, adj) path_segs[e][i] = segs hh_shaped.sort(key = lambda k:k['x']) # sort by midpoint y-coordinate for x, info in groupby(hh_shaped, key = lambda k:k['x']): # determine overlap for all path segments with same midpoint y-coordinate segs_to_adjust = [] for c in combinations(list(info), 2): min_y1 = min(c[0]['start'].imag, c[0]['end'].imag) max_y1 = max(c[0]['start'].imag, c[0]['end'].imag) min_y2 = min(c[1]['start'].imag, c[1]['end'].imag) max_y2 = max(c[1]['start'].imag, c[1]['end'].imag) if max(min_y1, min_y2) < min(max_y1, max_y2): # paths have overlap! if len(segs_to_adjust) == 0: segs_to_adjust.append(list(c)) else: for lst in segs_to_adjust: if c[0] in lst and not c[1] in lst: lst.append(c[1]) break elif c[1] in lst and not c[0] in lst: lst.append(c[0]) break else: # if neither c[0] or c[1] in any lst: segs_to_adjust.append(list(c)) for lst in segs_to_adjust: lst.sort(key = lambda k:k['start'].real) l = len(lst) mp_adj = [(l*-5 +5) + i*10 for i in range(l)] # e.g. [-10, 0, 10] when l = 3 for n in range(l): dx = lst[n]['dx'] e, i = lst[n]['e,i'] start = lst[n]['start'] end = lst[n]['end'] adj = [cof_adj[e], 0, 0.5*abs(dy) -cof_adj[e] + mp_adj[n]] segs = get_path_segments(start, end, 'h', 'h', max_bend, adj) path_segs[e][i] = segs # check if still overlap # if overlap, try to adjust path midpoint until there is no overlap print 'checking path/label overlap...' for e in edges: for i in range(len(path_segs[e])): seg = path_segs[e][i] p = Path(*seg) # get 100 (x + yj) points on the path points = [p.point(t) for t in [k/100. for k in range(101)]] overlap =[] for n in s_nodes.difference({e[1]}): # check if any points are inside species labels overlap.append(overlapping(points, pos[n], label_size[n])) if any(overlap): start = complex(*p_nodes[e][i]) end = complex(*p_nodes[e][i+1]) if direction[e][i] == 'v' and start.real==end.real or direction[e][i] == 'h' and start.imag == end.imag: print 'could not find non-overlapping path for {}'.format(e) elif direction[e][i] == direction[e][i+1]: print 'adjusting path {} to prevent path/label overlap...'.format(e) adjustments = [] if direction[e][i] == 'h': len_seg = 0.5*abs(end.real - start.real) else: len_seg = 0.5*abs(end.imag - start.imag) len_seg = len_seg - cof_adj[e] for k in range(1, int(2*len_seg/30)+1): adjustments.append(30*k) for dn in range(2,6): # move middle path segments toward reaction node for nm in range(1, dn): adj = nm*(len_seg/dn) if adj not in adjustments: adjustments.append(adj) for dn in range(2,6): # move middle path segments toward species node for nm in range(1, dn): adj = len_seg + (dn-nm)*(len_seg/dn) if adj not in adjustments: adjustments.append(adj) # adjust, calculate new path and check for overlap adjusted_paths = [] num_overlap = [] for adj in adjustments: print '.', sys.stdout.flush() new_segs = get_path_segments(start, end, direction[e][i], direction[e][i+1], max_bend, adjust=[0,0,adj]) p = Path(*new_segs) points = [p.point(t) for t in [k/100. for k in range(101)]] overlap = [] for n in s_nodes.difference({e[1]}): overlap.append(overlapping(points, pos[n], label_size[n])) num_overlap.append(sum(overlap)) adjusted_paths.append(new_segs) if not any(overlap): print 'ok' path_segs[e][i] = new_segs break else: print 'could not find non-overlapping path' path_segs[e][i] = adjusted_paths[num_overlap.index(min(num_overlap))] svg_paths = arc_paths for e in edges: p = [] for segs in path_segs[e]: p+=segs svg_paths[e] = Path(*p).d() for r in cofactors: for s in cofactors[r]: svg_paths[(r,s)] = cofactors[r][s]['path'] return svg_paths, pos
SVG2 = ''' </g> </svg> ''' svgpath1 = '''<path style="fill:none;fill-rule:evenodd;stroke:#{0:02x}{1:02x}{2:02x};stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' print(SVG1) for p in paths: path = parse_path(p.attrib["d"]) start = None myrange = list(frange(0, 1, decimal.Decimal('0.01'))) mycolors = list(red.range_to(blue, len(myrange))) for i, px in enumerate(myrange): new_path = Path() if start is None: start = path.point(float(px)) else: end = path.point(float(px)) new_path.append(Line(start, end)) start = end color = mycolors[i] r = int(color.red * 255) g = int(color.green * 255) b = int(color.blue * 255) print((svgpath1 + new_path.d() + svgpath2).format(clamp(r), clamp(g), clamp(b))) print(SVG2)
def test_Path_q_bezier_adds_relative_quadratic_bezier_command(): path = Path((0, 1), style='xyz') path.q_bezier((3, 4), (5, 6), relative=True) assert str(path) == '<path d="M 0 1 q 3 4, 5 6" style="xyz"/>\n'
def test_Path_c_bezier_adds_relative_smooth_bezier_command_if_no_first_point(): path = Path((0, 1), style='xyz').c_bezier(None, (3, 4), (5, 6), relative=True) assert str(path) == '<path d="M 0 1 s 3 4, 5 6" style="xyz"/>\n'
def test_Path_line_to_adds_a_relative_line_command_to_the_path(): path = Path((0, 1), style="xyz").line_to((2, 3), relative=True) assert str(path) == '<path d="M 0 1 l 2 3" style="xyz"/>\n'
def remove_redundant_lines(self): eps = 0.001 def get_slope_intersect(p1, p2): if abs(p1.real - p2.real) < eps: # Vertical; no slope and return x-intercept return None, p1.real m = (p2.imag - p1.imag) / (p2.real - p1.real) b1 = p1.imag - m * p1.real b2 = p2.imag - m * p2.real assert abs(b1 - b2) < eps return m, b1 lines_bucketed_by_slope_intersect = defaultdict(list) paths = self.svg_node.getElementsByTagName('path') overall_index = 0 for path_index, path in enumerate(paths): path_text = path.attributes['d'].value path_obj = parse_path(path_text) for line_index, line in enumerate(path_obj): slope, intersect = get_slope_intersect(line.start, line.end) if slope is not None: slope = round(slope, ndigits=3) intersect = round(intersect, ndigits=3) lines_bucketed_by_slope_intersect[(slope, intersect)].append({ 'overall_index': overall_index, 'path_index': path_index, 'line_index': line_index, 'line': line, }) overall_index += 1 to_remove = {} for slope_intersect, lines in lines_bucketed_by_slope_intersect.items(): # Naive N^2 search for overlapping lines within a slope-intersect bucket for i in range(len(lines)): line1 = lines[i]['line'] eq1 = get_slope_intersect(line1.start, line1.end) for j in range(i + 1, len(lines)): line2 = lines[j]['line'] eq2 = get_slope_intersect(line2.start, line2.end) # Compare slopes if (eq1[0] is None and eq2[0] is None) \ or (eq1[0] is not None and eq2[0] is not None and abs(eq1[0] - eq2[0]) < eps): # Compare intersects if abs(eq1[1] - eq2[1]) < eps: # Must be collinear, check for overlap l1x1 = min(line1.start.real, line1.end.real) l1x2 = max(line1.start.real, line1.end.real) l1y1 = min(line1.start.imag, line1.end.imag) l1y2 = max(line1.start.imag, line1.end.imag) l2x1 = min(line2.start.real, line2.end.real) l2x2 = max(line2.start.real, line2.end.real) l2y1 = min(line2.start.imag, line2.end.imag) l2y2 = max(line2.start.imag, line2.end.imag) if l1x1 <= l2x1 + eps and l1x2 + eps >= l2x2 and l1y1 <= l2y1 + eps and l1y2 + eps >= l2y2: # Overlapping, line 1 is bigger assert line1.length() + eps >= line2.length() to_remove[lines[j]['overall_index']] = (lines[j]['path_index'], lines[j]['line_index'], line2) elif l1x1 + eps >= l2x1 and l1x2 <= l2x2 + eps and l1y1 + eps >= l2y1 and l1y2 <= l2y2 + eps: # Overlapping, line 2 is bigger assert line2.length() + eps >= line1.length() to_remove[lines[i]['overall_index']] = (lines[i]['path_index'], lines[i]['line_index'], line1) # Reconstruct the paths, but excluding the redundant lines we just identified i = 0 removed = 0 removed_length = 0 kept = 0 kept_length = 0 for path_index, path in enumerate(paths): path_text = path.attributes['d'].value path_obj = parse_path(path_text) filtered_path = Path() for line_index, line in enumerate(path_obj): if i in to_remove: assert path_index == to_remove[i][0] assert line_index == to_remove[i][1] assert line == to_remove[i][2] removed += 1 removed_length += line.length() else: filtered_path.append(line) kept += 1 kept_length += line.length() i += 1 # Update the path data with the filtered path data path.attributes['d'] = filtered_path.d() print 'Removed {} lines ({} length) and kept {} lines ({} length)'.format( removed, removed_length, kept, kept_length, ) return [to_remove[k][2] for k in to_remove]
def test_Path_q_bezier_adds_smooth_quadratic_bezier_command_if_no_ctrl_pt(): path = Path((0, 1), style='xyz') path.q_bezier(None, (5, 6)) assert str(path) == '<path d="M 0 1 T 5 6" style="xyz"/>\n'
def test_Path_v_line_to_adds_a_relative_vertical_line_command_to_the_path(): path = Path((0, 1), style="xyz") path.v_line(-1.2, relative=True) assert str(path) == '<path d="M 0 1 v -1.2" style="xyz"/>\n'
def test_Path_v_line_to_adds_a_vertical_line_command_to_the_path(): path = Path((0, 1), style="xyz") path.v_line(3.11) assert str(path) == '<path d="M 0 1 V 3.11" style="xyz"/>\n'
def test_Path_h_line_to_adds_a_relative_horizontal_line_command_to_the_path(): path = Path((0, 1), style="xyz") path.h_line(7, relative=True) assert str(path) == '<path d="M 0 1 h 7" style="xyz"/>\n'
def test_Path_q_bezier_adds_relative_smooth_quad_bezier_cmd_if_no_ctrl_pt(): path = Path((0, 1), style='xyz').q_bezier(None, (5, 6), relative=True) assert str(path) == '<path d="M 0 1 t 5 6" style="xyz"/>\n'
def test_Path_init_makes_path_tag_with_starting_point_and_additional_params(): path = Path((5, 15), style="xyz") assert str(path) == '<path d="M 5 15" style="xyz"/>\n'
def parse_polygon(points: str) -> Path: coordinates = [tuple(map(float, c.split(','))) for c in points.split()] path = Path() for a, b in pairwise(coordinates): path.append(Line(complex(*a), complex(*b))) return path
def test_Path_c_bezier_adds_a_relative_cubic_bezier_command_to_the_path(): path = Path((0, 1), style='xyz') path.c_bezier((1, 2), (3, 4), (5, 6), relative=True) assert str(path) == '<path d="M 0 1 c 1 2, 3 4, 5 6" style="xyz"/>\n'
def test_Path_move_to_adds_a_move_command_to_the_path(): path = Path((0, 1), style="xyz") path.move_to((2, 3)) assert str(path) == '<path d="M 0 1 M 2 3" style="xyz"/>\n'
def test_Path_h_line_to_adds_a_horizontal_line_command_to_the_path(): path = Path((0, 1), style="xyz") path.h_line(5) assert str(path) == '<path d="M 0 1 H 5" style="xyz"/>\n'
def test_Path_arc_to_adds_relative_arc_command_to_the_path(): path = Path((0, 1), style='xyz') path.arc_to((13, 14), -30, False, False, (15, 16), relative=True) assert str(path) == '<path d="M 0 1 a 13 14 -30 0 0 15 16" style="xyz"/>\n' path = Path((0, 1), style='xyz') path.arc_to((13, 14), -30, False, True, (15, 16), relative=True) assert str(path) == '<path d="M 0 1 a 13 14 -30 0 1 15 16" style="xyz"/>\n' path = Path((0, 1), style='xyz') path.arc_to((13, 14), -30, True, False, (15, 16), relative=True) assert str(path) == '<path d="M 0 1 a 13 14 -30 1 0 15 16" style="xyz"/>\n' path = Path((0, 1), style='xyz').arc_to((13, 14), -30, True, True, (15, 16), relative=True) assert str(path) == '<path d="M 0 1 a 13 14 -30 1 1 15 16" style="xyz"/>\n'