def add_nodes(self, G, graph_element): nodes_element = Element('nodes') for node,data in G.nodes_iter(data=True): node_data=data.copy() # node_id=node_data.pop('id',make_str(node)) kw={'id':node_id} label=node_data.pop('label',make_str(node)) kw['label']=label pid=node_data.pop('pid',False) if pid: kw['pid']=pid if 'start' in node_data: kw['start']=make_str(node_data.pop('start')) if 'end' in node_data: kw['end']=make_str(node_data.pop('end')) # add node element with attributes node_element = Element("node", **kw) # add node element and attr subelements default=G.graph.get('node_default',{}) node_data=self.add_parents(node_element, node_data) if self.version=='1.1': node_data=self.add_slices(node_element, node_data) else: node_data=self.add_spells(node_element, node_data) node_data=self.add_viz(node_element,node_data) node_data=self.add_attributes("node", node_element, node_data, default) nodes_element.append(node_element) graph_element.append(nodes_element)
def add_nodes(self, G, graph_element): nodes_element = Element("nodes") for node, data in G.nodes(data=True): node_data = data.copy() node_id = make_str(node_data.pop("id", node)) kw = {"id": node_id} label = make_str(node_data.pop("label", node)) kw["label"] = label try: pid = node_data.pop("pid") kw["pid"] = make_str(pid) except KeyError: pass # add node element with attributes node_element = Element("node", **kw) # add node element and attr subelements default = G.graph.get("node_default", {}) node_data = self.add_parents(node_element, node_data) if self.version == "1.1": node_data = self.add_slices(node_element, node_data) else: node_data = self.add_spells(node_element, node_data) node_data = self.add_viz(node_element, node_data) node_data = self.add_attributes("node", node_element, node_data, default) nodes_element.append(node_element) graph_element.append(nodes_element)
def add_nodes(self, G, graph_element): nodes_element = Element("nodes") for node, data in G.nodes_iter(data=True): node_data = data.copy() # node_id = node_data.pop("id", make_str(node)) kw = {"id": node_id} label = node_data.pop("label", make_str(node)) kw["label"] = label pid = node_data.pop("pid", False) if pid: kw["pid"] = pid # add node element with attributes node_element = Element("node", **kw) # add node element and attr subelements default = G.graph.get("node_default", {}) node_data = self.add_parents(node_element, node_data) node_data = self.add_slices(node_element, node_data) node_data = self.add_viz(node_element, node_data) node_data = self.add_attributes("node", node_element, node_data, default) nodes_element.append(node_element) graph_element.append(nodes_element)
def add_attributes(self, node_or_edge, xml_obj, data, default): # Add attrvalues to node or edge attvalues = Element("attvalues") if len(data) == 0: return data if "start" in data or "end" in data: mode = "dynamic" else: mode = "static" for k, v in list(data.items()): # rename generic multigraph key to avoid any name conflict if k == "key": k = "networkx_key" attr_id = self.get_attr_id(make_str(k), self.xml_type[type(v)], node_or_edge, default, mode) if type(v) == list: # dynamic data for val, start, end in v: e = Element("attvalue") e.attrib["for"] = attr_id e.attrib["value"] = make_str(val) e.attrib["start"] = make_str(start) e.attrib["end"] = make_str(end) attvalues.append(e) else: # static data e = Element("attvalue") e.attrib["for"] = attr_id e.attrib["value"] = make_str(v) attvalues.append(e) xml_obj.append(attvalues) return data
def add_edges(self, G, graph_element): def edge_key_data(G): # helper function to unify multigraph and graph edge iterator if G.is_multigraph(): for u, v, key, data in G.edges_iter(data=True, keys=True): edge_data = data.copy() edge_data.update(key=key) edge_id = edge_data.pop("id", None) if edge_id is None: edge_id = next(self.edge_id) yield u, v, edge_id, edge_data else: for u, v, data in G.edges_iter(data=True): edge_data = data.copy() edge_id = edge_data.pop("id", None) if edge_id is None: edge_id = next(self.edge_id) yield u, v, edge_id, edge_data edges_element = Element("edges") for u, v, key, edge_data in edge_key_data(G): kw = {"id": make_str(key)} edge_weight = edge_data.pop("weight", False) if edge_weight: kw["weight"] = make_str(edge_weight) edge_type = edge_data.pop("type", False) if edge_type: kw["type"] = make_str(edge_type) edge_element = Element("edge", source=make_str(u), target=make_str(v), **kw) default = G.graph.get("edge_default", {}) edge_data = self.add_viz(edge_element, edge_data) edge_data = self.add_attributes("edge", edge_element, edge_data, default) edges_element.append(edge_element) graph_element.append(edges_element)
def add_graph_element(self, G): """ Serialize graph G in GraphML to the stream. """ if G.is_directed(): default_edge_type = 'directed' else: default_edge_type = 'undirected' graphid = G.graph.pop('id', None) if graphid is None: graph_element = self.myElement("graph", edgedefault=default_edge_type) else: graph_element = self.myElement("graph", edgedefault=default_edge_type, id=graphid) default = {} data = dict((k, v) for k, v in G.graph.items() if k not in ['node_default', 'edge_default']) self.add_attributes("graph", graph_element, data, default) self.add_nodes(G, graph_element) self.add_edges(G, graph_element) # self.attributes contains a mapping from XML Objects to a list of # data that needs to be added to them. # We postpone processing in order to do type inference/generalization. # See self.attr_type for (xml_obj, data) in self.attributes.items(): for (k, v, scope, default) in data: xml_obj.append(self.add_data(make_str(k), self.attr_type(k, scope, v), make_str(v), scope, default)) self.xml.append(graph_element)
def add_attributes(self, node_or_edge, xml_obj, data, default): # Add attrvalues to node or edge attvalues=Element('attvalues') if len(data)==0: return data if 'start' in data or 'end' in data: mode='dynamic' else: mode='static' for k,v in data.items(): # rename generic multigraph key to avoid any name conflict if k == 'key': k='networkx_key' attr_id = self.get_attr_id(make_str(k), self.xml_type[type(v)], node_or_edge, default, mode) if type(v)==list: # dynamic data for val,start,end in v: e=Element("attvalue") e.attrib['for']=attr_id e.attrib['value']=make_str(val) e.attrib['start']=make_str(start) e.attrib['end']=make_str(end) attvalues.append(e) else: # static data e=Element("attvalue") e.attrib['for']=attr_id e.attrib['value']=make_str(v) attvalues.append(e) xml_obj.append(attvalues) return data
def add_attributes(self, scope, xml_obj, data, default): """Appends attribute data.""" for k, v in data.items(): data_element = self.add_data(make_str(k), self.attr_type(make_str(k), scope, v), make_str(v), scope, default.get(k)) xml_obj.append(data_element)
def add_nodes(self, G, graph_element): nodes_element = Element('nodes') for node, data in G.nodes(data=True): node_data = data.copy() node_id = make_str(node_data.pop('id', node)) kw = {'id': node_id} label = make_str(node_data.pop('label', node)) kw['label'] = label try: pid = node_data.pop('pid') kw['pid'] = make_str(pid) except KeyError: pass # add node element with attributes node_element = Element('node', **kw) # add node element and attr subelements default = G.graph.get('node_default', {}) node_data = self.add_parents(node_element, node_data) if self.version == '1.1': node_data = self.add_slices(node_element, node_data) else: node_data = self.add_spells(node_element, node_data) node_data = self.add_viz(node_element, node_data) node_data = self.add_attributes('node', node_element, node_data, default) nodes_element.append(node_element) graph_element.append(nodes_element)
def add_attributes(self, scope, xml_obj, data, default): """Appends attributes to edges or nodes. """ for k, v in data.items(): default_value = default.get(k) obj = self.add_data(make_str(k), type(v), make_str(v), scope=scope, default=default_value) xml_obj.append(obj)
def to_pydot(N, strict=True): """Return a pydot graph from a NetworkX graph N. Parameters ---------- N : NetworkX graph A graph created with NetworkX Examples -------- >>> K5 = nx.complete_graph(5) >>> P = nx.nx_pydot.to_pydot(K5) Notes ----- """ import pydotplus # set Graphviz graph type if N.is_directed(): graph_type='digraph' else: graph_type='graph' strict=N.number_of_selfloops()==0 and not N.is_multigraph() name = N.name graph_defaults=N.graph.get('graph',{}) if name is '': P = pydotplus.Dot('', graph_type=graph_type, strict=strict, **graph_defaults) else: P = pydotplus.Dot('"%s"'%name, graph_type=graph_type, strict=strict, **graph_defaults) try: P.set_node_defaults(**N.graph['node']) except KeyError: pass try: P.set_edge_defaults(**N.graph['edge']) except KeyError: pass for n,nodedata in N.nodes_iter(data=True): str_nodedata=dict((k,make_str(v)) for k,v in nodedata.items()) p=pydotplus.Node(make_str(n),**str_nodedata) P.add_node(p) if N.is_multigraph(): for u,v,key,edgedata in N.edges_iter(data=True,keys=True): str_edgedata=dict((k,make_str(v)) for k,v in edgedata.items()) edge=pydotplus.Edge(make_str(u), make_str(v), key=make_str(key), **str_edgedata) P.add_edge(edge) else: for u,v,edgedata in N.edges_iter(data=True): str_edgedata=dict((k,make_str(v)) for k,v in edgedata.items()) edge=pydotplus.Edge(make_str(u),make_str(v),**str_edgedata) P.add_edge(edge) return P
def node_link_graph(data, directed=False, multigraph=True): """Return graph from node-link data format. Parameters ---------- data : dict node-link formatted graph data directed : bool If True, and direction not specified in data, return a directed graph. multigraph : bool If True, and multigraph not specified in data, return a multigraph. Returns ------- G : NetworkX graph A NetworkX graph object Examples -------- >>> from networkx.readwrite import json_graph >>> G = nx.Graph([(1,2)]) >>> data = json_graph.node_link_data(G) >>> H = json_graph.node_link_graph(data) See Also -------- node_link_data, adjacency_data, tree_data """ multigraph = data.get('multigraph',multigraph) directed = data.get('directed',directed) if multigraph: graph = nx.MultiGraph() else: graph = nx.Graph() if directed: graph = graph.to_directed() mapping=[] graph.graph = dict(data.get('graph',[])) c = count() for d in data['nodes']: node = d.get('id',next(c)) mapping.append(node) nodedata = dict((make_str(k),v) for k,v in d.items() if k!='id') graph.add_node(node, **nodedata) for d in data['links']: link_data = d.copy() source = link_data.pop('source') target = link_data.pop('target') edgedata = dict((make_str(k),v) for k,v in d.items() if k!='source' and k!='target') graph.add_edge(mapping[source],mapping[target],**edgedata) return graph
def _place_initial_states(trs_graph, pd_graph, tikz): init_subg = pydot.Subgraph("initial") init_subg.set_rank("source") for node in trs_graph.states.initial: pd_node = pydot.Node(make_str(node)) init_subg.add_node(pd_node) phantom_node = "phantominit" + str(node) pd_node = pydot.Node(make_str(phantom_node)) init_subg.add_node(pd_node) pd_graph.add_subgraph(init_subg)
def add_spells(self,node_or_edge_element,node_or_edge_data): spells=node_or_edge_data.pop('spells',False) if spells: spells_element=Element('spells') for start,end in spells: e=Element('spell') if start is not None: e.attrib['start']=make_str(start) if end is not None: e.attrib['end']=make_str(end) spells_element.append(e) node_or_edge_element.append(spells_element) return node_or_edge_data
def _place_initial_states(trs_graph, pd_graph, tikz): init_subg = pydot.Subgraph('initial') init_subg.set_rank('source') for node in trs_graph.states.initial: pd_node = pydot.Node(make_str(node)) init_subg.add_node(pd_node) phantom_node = 'phantominit' + str(node) pd_node = pydot.Node(make_str(phantom_node)) init_subg.add_node(pd_node) pd_graph.add_subgraph(init_subg)
def add_edges(self, G, graph_element): if G.is_multigraph(): for u, v, key, data in G.edges_iter(data=True, keys=True): edge_element = Element("edge", source=make_str(u), target=make_str(v)) default = G.graph.get("edge_default", {}) self.add_attributes("edge", edge_element, data, default) self.add_attributes("edge", edge_element, {"key": key}, default) graph_element.append(edge_element) else: for u, v, data in G.edges_iter(data=True): edge_element = Element("edge", source=make_str(u), target=make_str(v)) default = G.graph.get("edge_default", {}) self.add_attributes("edge", edge_element, data, default) graph_element.append(edge_element)
def add_spells(self, node_or_edge_element, node_or_edge_data): spells = node_or_edge_data.pop("spells", False) if spells: spells_element = Element("spells") for start, end in spells: e = Element("spell") if start is not None: e.attrib["start"] = make_str(start) self.alter_graph_mode_timeformat(start) if end is not None: e.attrib["end"] = make_str(end) self.alter_graph_mode_timeformat(end) spells_element.append(e) node_or_edge_element.append(spells_element) return node_or_edge_data
def generate_adjlist(G, delimiter=' '): """Generate a single line of the graph G in adjacency list format. Parameters ---------- G : NetworkX graph delimiter : string, optional Separator for node labels Returns ------- lines : string Lines of data in adjlist format. Examples -------- >>> G = nx.lollipop_graph(4, 3) >>> for line in nx.generate_adjlist(G): ... print(line) 0 1 2 3 1 2 3 2 3 3 4 4 5 5 6 6 See Also -------- write_adjlist, read_adjlist """ directed = G.is_directed() seen = set() for s, nbrs in G.adjacency_iter(): line = make_str(s) + delimiter for t, data in nbrs.items(): if not directed and t in seen: continue if G.is_multigraph(): for d in data.values(): line += make_str(t) + delimiter else: line += make_str(t) + delimiter if not directed: seen.add(s) yield line[:-len(delimiter)]
def add_attributes(self, scope, xml_obj, data, default): """Appends attribute data to edges or nodes, and stores type information to be added later. See add_graph_element. """ for k, v in data.items(): self.attribute_types[(make_str(k), scope)].add(type(v)) self.attributes[xml_obj].append([k, v, scope, default.get(k)])
def get_attr_id(self, title, attr_type, edge_or_node, default, mode): # find the id of the attribute or generate a new id try: return self.attr[edge_or_node][mode][title] except KeyError: # generate new id new_id = str(next(self.attr_id)) self.attr[edge_or_node][mode][title] = new_id attr_kwargs = {'id': new_id, 'title': title, 'type': attr_type} attribute = Element('attribute', **attr_kwargs) # add subelement for data default value if present default_title = default.get(title) if default_title is not None: default_element = Element('default') default_element.text = make_str(default_title) attribute.append(default_element) # new insert it into the XML attributes_element = None for a in self.graph_element.findall('attributes'): # find existing attributes element by class and mode a_class = a.get('class') a_mode = a.get('mode', 'static') # default mode is static if a_class == edge_or_node and a_mode == mode: attributes_element = a if attributes_element is None: # create new attributes element attr_kwargs = {'mode': mode, 'class': edge_or_node} attributes_element = Element('attributes', **attr_kwargs) self.graph_element.insert(0, attributes_element) attributes_element.append(attribute) return new_id
def pydot_layout(G,prog='neato',root=None, **kwds): """Create node positions using Pydot and Graphviz. Returns a dictionary of positions keyed by node. Examples -------- >>> G=nx.complete_graph(4) >>> pos=nx.pydot_layout(G) >>> pos=nx.pydot_layout(G,prog='dot') """ try: import pydot except ImportError: raise ImportError('pydot_layout() requires pydot ', 'http://code.google.com/p/pydot/') P=to_pydot(G) if root is not None : P.set("root",make_str(root)) D=P.create_dot(prog=prog) if D=="": # no data returned print("Graphviz layout with %s failed"%(prog)) print() print("To debug what happened try:") print("P=pydot_from_networkx(G)") print("P.write_dot(\"file.dot\")") print("And then run %s on file.dot"%(prog)) return Q=pydot.graph_from_dot_data(D) node_pos={} for n in G.nodes(): pydot_node = pydot.Node(make_str(n)).get_name().encode('utf-8') node=Q.get_node(pydot_node) if isinstance(node,list): node=node[0] pos=node.get_pos()[1:-1] # strip leading and trailing double quotes if pos != None: xx,yy=pos.split(",") node_pos[n]=(float(xx),float(yy)) return node_pos
def add_edges(self, G, graph_element): def edge_key_data(G): # helper function to unify multigraph and graph edge iterator if G.is_multigraph(): for u,v,key,data in G.edges_iter(data=True,keys=True): edge_data=data.copy() edge_data.update(key=key) edge_id=edge_data.pop('id',None) if edge_id is None: edge_id=next(self.edge_id) yield u,v,edge_id,edge_data else: for u,v,data in G.edges_iter(data=True): edge_data=data.copy() edge_id=edge_data.pop('id',None) if edge_id is None: edge_id=next(self.edge_id) yield u,v,edge_id,edge_data edges_element = Element('edges') for u,v,key,edge_data in edge_key_data(G): kw={'id':make_str(key)} try: edge_weight=edge_data.pop('weight') kw['weight']=make_str(edge_weight) except KeyError: pass try: edge_type=edge_data.pop('type') kw['type']=make_str(edge_type) except KeyError: pass source_id = make_str(G.node[u].get('id', u)) target_id = make_str(G.node[v].get('id', v)) edge_element = Element("edge", source=source_id,target=target_id, **kw) default=G.graph.get('edge_default',{}) if self.version == '1.1': edge_data=self.add_slices(edge_element, edge_data) else: edge_data=self.add_spells(edge_element, edge_data) edge_data=self.add_viz(edge_element,edge_data) edge_data=self.add_attributes("edge", edge_element, edge_data, default) edges_element.append(edge_element) graph_element.append(edges_element)
def add_children(parent, children): for data in children: child = data['id'] graph.add_edge(parent, child) grandchildren = data.get('children',[]) if grandchildren: add_children(child,grandchildren) nodedata = dict((make_str(k),v) for k,v in data.items() if k!='id' and k!='children') graph.add_node(child,attr_dict=nodedata)
def add_children(parent, children_): for data in children_: child = data[id_] graph.add_edge(parent, child) grandchildren = data.get(children, []) if grandchildren: add_children(child, grandchildren) nodedata = dict((make_str(k), v) for k, v in data.items() if k != id_ and k != children) graph.add_node(child, **nodedata)
def tree_graph(data, attrs=_attrs): """Return graph from tree data format. Parameters ---------- data : dict Tree formatted graph data Returns ------- G : NetworkX DiGraph attrs : dict A dictionary that contains two keys 'id' and 'children'. The corresponding values provide the attribute names for storing NetworkX-internal graph data. The values should be unique. Default value: :samp:`dict(id='id', children='children')`. Examples -------- >>> from networkx.readwrite import json_graph >>> G = nx.DiGraph([(1,2)]) >>> data = json_graph.tree_data(G,root=1) >>> H = json_graph.tree_graph(data) Notes ----- The default value of attrs will be changed in a future release of NetworkX. See Also -------- tree_graph, node_link_data, adjacency_data """ graph = nx.DiGraph() id_ = attrs['id'] children = attrs['children'] def add_children(parent, children_): for data in children_: child = data[id_] graph.add_edge(parent, child) grandchildren = data.get(children, []) if grandchildren: add_children(child, grandchildren) nodedata = dict((make_str(k), v) for k, v in data.items() if k != id_ and k != children) graph.add_node(child, **nodedata) root = data[id_] children_ = data.get(children, []) nodedata = dict((make_str(k), v) for k, v in data.items() if k != id_ and k != children) graph.add_node(root, **nodedata) add_children(root, children_) return graph
def add_data(self, name, element_type, value, scope="all", default=None): """ Make a data element for an edge or a node. Keep a log of the type in the keys table. """ if element_type not in self.xml_type: raise nx.NetworkXError("GraphML writer does not support " "%s as data values." % element_type) key_id = self.get_key(name, self.xml_type[element_type], scope, default) data_element = Element("data", key=key_id) data_element.text = make_str(value) return data_element
def add_edges(self, G, graph_element): def edge_key_data(G): # helper function to unify multigraph and graph edge iterator if G.is_multigraph(): for u, v, data, key in G.edges(data=True, keys=True): edge_data = data.copy() edge_data.update(key=key) edge_id = edge_data.pop("id", None) if edge_id is None: edge_id = next(self.edge_id) yield u, v, edge_id, edge_data else: for u, v, data in G.edges(data=True): edge_data = data.copy() edge_id = edge_data.pop("id", None) if edge_id is None: edge_id = next(self.edge_id) yield u, v, edge_id, edge_data edges_element = Element("edges") for u, v, key, edge_data in edge_key_data(G): kw = {"id": make_str(key)} try: edge_weight = edge_data.pop("weight") kw["weight"] = make_str(edge_weight) except KeyError: pass try: edge_type = edge_data.pop("type") kw["type"] = make_str(edge_type) except KeyError: pass try: start = edge_data.pop("start") kw["start"] = make_str(start) self.alter_graph_mode_timeformat(start) except KeyError: pass try: end = edge_data.pop("end") kw["end"] = make_str(end) self.alter_graph_mode_timeformat(end) except KeyError: pass source_id = make_str(G.node[u].get("id", u)) target_id = make_str(G.node[v].get("id", v)) edge_element = Element("edge", source=source_id, target=target_id, **kw) default = G.graph.get("edge_default", {}) if self.version == "1.1": edge_data = self.add_slices(edge_element, edge_data) else: edge_data = self.add_spells(edge_element, edge_data) edge_data = self.add_viz(edge_element, edge_data) edge_data = self.add_attributes("edge", edge_element, edge_data, default) edges_element.append(edge_element) graph_element.append(edges_element)
def edge_key_data(G): # helper function to unify multigraph and graph edge iterator if G.is_multigraph(): for u, v, key, data in G.edges(data=True, keys=True): edge_data = data.copy() edge_data.update(key=key) edge_id = edge_data.pop('id', None) if edge_id is None: edge_id = next(self.edge_id) while make_str(edge_id) in self.all_edge_ids: edge_id = next(self.edge_id) self.all_edge_ids.add(make_str(edge_id)) yield u, v, edge_id, edge_data else: for u, v, data in G.edges(data=True): edge_data = data.copy() edge_id = edge_data.pop('id', None) if edge_id is None: edge_id = next(self.edge_id) while make_str(edge_id) in self.all_edge_ids: edge_id = next(self.edge_id) self.all_edge_ids.add(make_str(edge_id)) yield u, v, edge_id, edge_data
def get_key(self, name, attr_type, scope, default): keys_key = (name, attr_type, scope) try: return self.keys[keys_key] except KeyError: new_id = "d%i" % len(list(self.keys)) self.keys[keys_key] = new_id key_kwargs = {"id": new_id, "for": scope, "attr.name": name, "attr.type": attr_type} key_element = Element("key", **key_kwargs) # add subelement for data default value if present if default is not None: default_element = Element("default") default_element.text = make_str(default) key_element.append(default_element) self.xml.insert(0, key_element) return new_id
def add_attributes(self, node_or_edge, xml_obj, data, default): # Add attrvalues to node or edge attvalues = Element('attvalues') if len(data) == 0: return data mode = 'static' for k, v in data.items(): # rename generic multigraph key to avoid any name conflict if k == 'key': k = 'networkx_key' val_type = type(v) if val_type not in self.xml_type: raise TypeError('attribute value type is not allowed: %s' % val_type) if isinstance(v, list): # dynamic data for val, start, end in v: val_type = type(val) if start is not None or end is not None: mode = 'dynamic' self.alter_graph_mode_timeformat(start) self.alter_graph_mode_timeformat(end) break attr_id = self.get_attr_id(make_str(k), self.xml_type[val_type], node_or_edge, default, mode) for val, start, end in v: e = Element('attvalue') e.attrib['for'] = attr_id e.attrib['value'] = make_str(val) if start is not None: e.attrib['start'] = make_str(start) if end is not None: e.attrib['end'] = make_str(end) attvalues.append(e) else: # static data mode = 'static' attr_id = self.get_attr_id(make_str(k), self.xml_type[val_type], node_or_edge, default, mode) e = Element('attvalue') e.attrib['for'] = attr_id if isinstance(v, bool): e.attrib['value'] = make_str(v).lower() else: e.attrib['value'] = make_str(v) attvalues.append(e) xml_obj.append(attvalues) return data
def add_attributes(self, node_or_edge, xml_obj, data, default): # Add attrvalues to node or edge attvalues = Element('attvalues') if len(data) == 0: return data mode = 'static' for k, v in data.items(): # rename generic multigraph key to avoid any name conflict if k == 'key': k = 'networkx_key' val_type = type(v) if val_type not in self.xml_type: raise TypeError('attribute value type is not allowed: %s' % val_type) if isinstance(v, list): # dynamic data for val, start, end in v: val_type = type(val) if start is not None or end is not None: mode = 'dynamic' self.alter_graph_mode_timeformat(start) self.alter_graph_mode_timeformat(end) break attr_id = self.get_attr_id(make_str(k), self.xml_type[val_type], node_or_edge, default, mode) for val, start, end in v: e = Element('attvalue') e.attrib['for'] = attr_id e.attrib['value'] = make_str(val) # Handle nan, inf, -inf differently if val_type == float: if e.attrib['value'] == 'inf': e.attrib['value'] = 'INF' elif e.attrib['value'] == 'nan': e.attrib['value'] = 'NaN' elif e.attrib['value'] == '-inf': e.attrib['value'] = '-INF' if start is not None: e.attrib['start'] = make_str(start) if end is not None: e.attrib['end'] = make_str(end) attvalues.append(e) else: # static data mode = 'static' attr_id = self.get_attr_id(make_str(k), self.xml_type[val_type], node_or_edge, default, mode) e = Element('attvalue') e.attrib['for'] = attr_id if isinstance(v, bool): e.attrib['value'] = make_str(v).lower() else: e.attrib['value'] = make_str(v) # Handle float nan, inf, -inf differently if val_type == float: if e.attrib['value'] == 'inf': e.attrib['value'] = 'INF' elif e.attrib['value'] == 'nan': e.attrib['value'] = 'NaN' elif e.attrib['value'] == '-inf': e.attrib['value'] = '-INF' attvalues.append(e) xml_obj.append(attvalues) return data
def pydot_layout(G, prog='neato', root=None, **kwds): """Create node positions using :mod:`pydot` and Graphviz. Parameters -------- G : Graph NetworkX graph to be laid out. prog : optional[str] Basename of the GraphViz command with which to layout this graph. Defaults to `neato`, the default GraphViz command for undirected graphs. Returns -------- dict Dictionary of positions keyed by node. Examples -------- >>> G = nx.complete_graph(4) >>> pos = nx.nx_pydot.pydot_layout(G) >>> pos = nx.nx_pydot.pydot_layout(G, prog='dot') """ pydot = _import_pydot() P = to_pydot(G) if root is not None: P.set("root", make_str(root)) # List of low-level bytes comprising a string in the dot language converted # from the passed graph with the passed external GraphViz command. D_bytes = P.create_dot(prog=prog) # Unique string decoded from these bytes with the preferred locale encoding. D = unicode(D_bytes, encoding=getpreferredencoding()) if D == "": # no data returned print("Graphviz layout with %s failed" % (prog)) print() print("To debug what happened try:") print("P = nx.nx_pydot.to_pydot(G)") print("P.write_dot(\"file.dot\")") print("And then run %s on file.dot" % (prog)) return # List of one or more "pydot.Dot" instances deserialized from this string. Q_list = pydot.graph_from_dot_data(D) assert len(Q_list) == 1 # The first and only such instance, as guaranteed by the above assertion. Q = Q_list[0] node_pos = {} for n in G.nodes(): pydot_node = pydot.Node(make_str(n)).get_name() node = Q.get_node(pydot_node) if isinstance(node, list): node = node[0] pos = node.get_pos()[1:-1] # strip leading and trailing double quotes if pos is not None: xx, yy = pos.split(",") node_pos[n] = (float(xx), float(yy)) return node_pos
def node_link_graph(data, directed=False, multigraph=True, attrs=_attrs): """Return graph from node-link data format. Parameters ---------- data : dict node-link formatted graph data directed : bool If True, and direction not specified in data, return a directed graph. multigraph : bool If True, and multigraph not specified in data, return a multigraph. attrs : dict A dictionary that contains four keys 'id', 'source', 'target' and 'key'. The corresponding values provide the attribute names for storing NetworkX-internal graph data. Default value: :samp:`dict(id='id', source='source', target='target', key='key')`. Returns ------- G : NetworkX graph A NetworkX graph object Examples -------- >>> from networkx.readwrite import json_graph >>> G = nx.Graph([(1,2)]) >>> data = json_graph.node_link_data(G) >>> H = json_graph.node_link_graph(data) Notes ----- The default value of attrs will be changed in a future release of NetworkX. See Also -------- node_link_data, adjacency_data, tree_data """ multigraph = data.get('multigraph', multigraph) directed = data.get('directed', directed) if multigraph: graph = nx.MultiGraph() else: graph = nx.Graph() if directed: graph = graph.to_directed() id_ = attrs['id'] source = attrs['source'] target = attrs['target'] # Allow 'key' to be omitted from attrs if the graph is not a multigraph. key = None if not multigraph else attrs['key'] mapping = [] graph.graph = data.get('graph', {}) c = count() for d in data['nodes']: node = d.get(id_, next(c)) mapping.append(node) nodedata = dict((make_str(k), v) for k, v in d.items() if k != id_) graph.add_node(node, **nodedata) for d in data['links']: src = d[source] tgt = d[target] if not multigraph: edgedata = dict((make_str(k), v) for k, v in d.items() if k != source and k != target) graph.add_edge(mapping[src], mapping[tgt], **edgedata) else: ky = d.get(key, None) edgedata = dict((make_str(k), v) for k, v in d.items() if k != source and k != target and k != key) graph.add_edge(mapping[src], mapping[tgt], ky, **edgedata) return graph
def generate_multiline_adjlist(G, delimiter = ' '): """Generate a single line of the graph G in multiline adjacency list format. Parameters ---------- G : NetworkX graph delimiter : string, optional Separator for node labels Returns ------- lines : string Lines of data in multiline adjlist format. Examples -------- >>> G = nx.lollipop_graph(4, 3) >>> for line in nx.generate_multiline_adjlist(G): ... print(line) 0 3 1 {} 2 {} 3 {} 1 2 2 {} 3 {} 2 1 3 {} 3 1 4 {} 4 1 5 {} 5 1 6 {} 6 0 See Also -------- write_multiline_adjlist, read_multiline_adjlist """ if G.is_directed(): if G.is_multigraph(): for s,nbrs in G.adjacency_iter(): nbr_edges=[ (u,data) for u,datadict in nbrs.items() for key,data in datadict.items()] deg=len(nbr_edges) yield make_str(s)+delimiter+"%i"%(deg) for u,d in nbr_edges: if d is None: yield make_str(u) else: yield make_str(u)+delimiter+make_str(d) else: # directed single edges for s,nbrs in G.adjacency_iter(): deg=len(nbrs) yield make_str(s)+delimiter+"%i"%(deg) for u,d in nbrs.items(): if d is None: yield make_str(u) else: yield make_str(u)+delimiter+make_str(d) else: # undirected if G.is_multigraph(): seen=set() # helper dict used to avoid duplicate edges for s,nbrs in G.adjacency_iter(): nbr_edges=[ (u,data) for u,datadict in nbrs.items() if u not in seen for key,data in datadict.items()] deg=len(nbr_edges) yield make_str(s)+delimiter+"%i"%(deg) for u,d in nbr_edges: if d is None: yield make_str(u) else: yield make_str(u)+delimiter+make_str(d) seen.add(s) else: # undirected single edges seen=set() # helper dict used to avoid duplicate edges for s,nbrs in G.adjacency_iter(): nbr_edges=[ (u,d) for u,d in nbrs.items() if u not in seen] deg=len(nbr_edges) yield make_str(s)+delimiter+"%i"%(deg) for u,d in nbr_edges: if d is None: yield make_str(u) else: yield make_str(u)+delimiter+make_str(d) seen.add(s)
def add_edges(self, G, graph_element): def edge_key_data(G): # helper function to unify multigraph and graph edge iterator if G.is_multigraph(): for u, v, key, data in G.edges(data=True, keys=True): edge_data = data.copy() edge_data.update(key=key) edge_id = edge_data.pop('id', None) if edge_id is None: edge_id = next(self.edge_id) while make_str(edge_id) in self.all_edge_ids: edge_id = next(self.edge_id) self.all_edge_ids.add(make_str(edge_id)) yield u, v, edge_id, edge_data else: for u, v, data in G.edges(data=True): edge_data = data.copy() edge_id = edge_data.pop('id', None) if edge_id is None: edge_id = next(self.edge_id) while make_str(edge_id) in self.all_edge_ids: edge_id = next(self.edge_id) self.all_edge_ids.add(make_str(edge_id)) yield u, v, edge_id, edge_data edges_element = Element('edges') for u, v, key, edge_data in edge_key_data(G): kw = {'id': make_str(key)} try: edge_weight = edge_data.pop('weight') kw['weight'] = make_str(edge_weight) except KeyError: pass try: edge_type = edge_data.pop('type') kw['type'] = make_str(edge_type) except KeyError: pass try: start = edge_data.pop('start') kw['start'] = make_str(start) self.alter_graph_mode_timeformat(start) except KeyError: pass try: end = edge_data.pop('end') kw['end'] = make_str(end) self.alter_graph_mode_timeformat(end) except KeyError: pass source_id = make_str(G.nodes[u].get('id', u)) target_id = make_str(G.nodes[v].get('id', v)) edge_element = Element('edge', source=source_id, target=target_id, **kw) default = G.graph.get('edge_default', {}) if self.version == '1.1': edge_data = self.add_slices(edge_element, edge_data) else: edge_data = self.add_spells(edge_element, edge_data) edge_data = self.add_viz(edge_element, edge_data) edge_data = self.add_attributes('edge', edge_element, edge_data, default) edges_element.append(edge_element) graph_element.append(edges_element)
def add_nodes(self, G, graph_element): for node, data in G.nodes_iter(data=True): node_element = Element("node", id=make_str(node)) default = G.graph.get('node_default', {}) self.add_attributes("node", node_element, data, default) graph_element.append(node_element)
def add_graph_element(self, G): """ Serialize graph G in GraphML to the stream. """ if G.is_directed(): default_edge_type = "directed" else: default_edge_type = "undirected" graphid = G.graph.pop("id", None) if graphid is None: graph_element = self._xml.element("graph", edgedefault=default_edge_type) else: graph_element = self._xml.element("graph", edgedefault=default_edge_type, id=graphid) # gather attributes types for the whole graph # to find the most general numeric format needed. # Then pass through attributes to create key_id for each. graphdata = { k: v for k, v in G.graph.items() if k not in ("node_default", "edge_default") } node_default = G.graph.get("node_default", {}) edge_default = G.graph.get("edge_default", {}) # Graph attributes for k, v in graphdata.items(): self.attribute_types[(make_str(k), "graph")].add(type(v)) for k, v in graphdata.items(): element_type = self.xml_type[self.attr_type(k, "graph", v)] self.get_key(make_str(k), element_type, "graph", None) # Nodes and data for node, d in G.nodes(data=True): for k, v in d.items(): self.attribute_types[(make_str(k), "node")].add(type(v)) for node, d in G.nodes(data=True): for k, v in d.items(): T = self.xml_type[self.attr_type(k, "node", v)] self.get_key(make_str(k), T, "node", node_default.get(k)) # Edges and data if G.is_multigraph(): for u, v, ekey, d in G.edges(keys=True, data=True): for k, v in d.items(): self.attribute_types[(make_str(k), "edge")].add(type(v)) for u, v, ekey, d in G.edges(keys=True, data=True): for k, v in d.items(): T = self.xml_type[self.attr_type(k, "edge", v)] self.get_key(make_str(k), T, "edge", edge_default.get(k)) else: for u, v, d in G.edges(data=True): for k, v in d.items(): self.attribute_types[(make_str(k), "edge")].add(type(v)) for u, v, d in G.edges(data=True): for k, v in d.items(): T = self.xml_type[self.attr_type(k, "edge", v)] self.get_key(make_str(k), T, "edge", edge_default.get(k)) # Now add attribute keys to the xml file for key in self.xml: self._xml.write(key, pretty_print=self._prettyprint) # The incremental_writer writes each node/edge as it is created incremental_writer = IncrementalElement(self._xml, self._prettyprint) with graph_element: self.add_attributes("graph", incremental_writer, graphdata, {}) self.add_nodes(G, incremental_writer) # adds attributes too self.add_edges(G, incremental_writer) # adds attributes too
def parse_pajek_(lines): import shlex import networkx as nx from networkx.utils import is_string_like, open_file, make_str # multigraph=False if is_string_like(lines): lines = iter(lines.split('\n')) lines = iter([line.rstrip('\n') for line in lines]) G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes labels = [] # in the order of the file, needed for matrix while lines: try: l = next(lines) except: # EOF break if l.lower().startswith("*network"): try: label, name = l.split(None, 1) except ValueError: # Line was not of the form: *network NAME pass else: G.graph['name'] = name elif l.lower().startswith("*vertices"): nodelabels = {} l, nnodes = l.split() for i in range(int(nnodes)): l = next(lines) try: splitline = [ x.decode('utf-8') for x in shlex.split(make_str(l).encode('utf-8')) ] except AttributeError: splitline = shlex.split(str(l)) id, label = splitline[0:2] labels.append(label) G.add_node(label) nodelabels[id] = label G.nodes[label]['id'] = id try: x, y, shape = splitline[2:5] G.nodes[label].update({ 'x': float(x), 'y': float(y), 'shape': shape }) except: pass extra_attr = zip(splitline[5::2], splitline[6::2]) G.nodes[label].update(extra_attr) elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"): if l.lower().startswith("*edge"): # switch from multidigraph to multigraph G = nx.MultiGraph(G) if l.lower().startswith("*arcs"): # switch to directed with multiple arcs for each existing edge G = G.to_directed() for l in lines: try: splitline = [ x.decode('utf-8') for x in shlex.split(make_str(l).encode('utf-8')) ] except AttributeError: splitline = shlex.split(str(l)) if len(splitline) < 2: continue ui, vi = splitline[0:2] u = nodelabels.get(ui, ui) v = nodelabels.get(vi, vi) # parse the data attached to this edge and put in a dictionary edge_data = {} try: # there should always be a single value on the edge? w = splitline[2:3] edge_data.update({'weight': float(w[0])}) except: pass # if there isn't, just assign a 1 # edge_data.update({'value':1}) extra_attr = zip(splitline[3::2], splitline[4::2]) edge_data.update(extra_attr) # if G.has_edge(u,v): # multigraph=True G.add_edge(u, v, **edge_data) elif l.lower().startswith("*matrix"): G = nx.DiGraph(G) adj_list = ((labels[row], labels[col], { 'weight': int(data) }) for (row, line) in enumerate(lines) for (col, data) in enumerate(line.split()) if int(data) != 0) G.add_edges_from(adj_list) return G
def generate_ordered_dot(N, name=None): """ The networkx write_dot() function generates """ try: import pydot except ImportError: raise ImportError('to_pydot() requires pydot: ' 'http://code.google.com/p/pydot/') # set Graphviz graph type if N.is_directed(): graph_type = 'digraph' else: graph_type = 'graph' strict = N.number_of_selfloops() == 0 and not N.is_multigraph() node_attrs = dict() node_attrs["shape"] = "circle" node_attrs["width"] = "0.1" node_attrs["height"] = "0.1" node_attrs["fixedsize"] = "true" node_attrs["label"] = "" graph_defaults = N.graph.get('graph', {}) graph_defaults["ratio"] = "auto" graph_defaults["labelloc"] = "t" graph_defaults["label"] = name graph_defaults["pad"] = "1.0" if name is None: P = pydot.Dot(graph_type=graph_type, strict=strict, **graph_defaults) else: P = pydot.Dot('"%s"' % name, graph_type=graph_type, strict=strict, **graph_defaults) try: P.set_node_defaults(**node_attrs) except KeyError: pass try: P.set_edge_defaults(**N.graph['edge']) except KeyError: pass for n, nodedata in sorted(N.nodes_iter(data=True), key=lambda n: int(n[0])): str_nodedata = dict((k, make_str(v)) for k, v in nodedata.items()) p = pydot.Node(make_str(n), **str_nodedata) P.add_node(p) if N.is_multigraph(): for u, v, key, edgedata in N.edges_iter(data=True, keys=True): str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items()) edge = pydot.Edge(make_str(u), make_str(v), key=make_str(key), **str_edgedata) P.add_edge(edge) else: for u, v, edgedata in sorted(N.edges_iter(data=True), key=lambda u: int(u[0])): str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items()) if int(v) < int(u): edge = pydot.Edge(make_str(v), make_str(u), **str_edgedata) else: edge = pydot.Edge(make_str(u), make_str(v), **str_edgedata) P.add_edge(edge) return P
def pydot_layout(G, prog='neato', root=None): """Create node positions using :mod:`pydot` and Graphviz. Parameters -------- G : Graph NetworkX graph to be laid out. prog : string (default: 'neato') Name of the GraphViz command to use for layout. Options depend on GraphViz version but may include: 'dot', 'twopi', 'fdp', 'sfdp', 'circo' root : Node from G or None (default: None) The node of G from which to start some layout algorithms. Returns -------- dict Dictionary of positions keyed by node. Examples -------- >>> G = nx.complete_graph(4) >>> pos = nx.nx_pydot.pydot_layout(G) >>> pos = nx.nx_pydot.pydot_layout(G, prog='dot') Notes ----- If you use complex node objects, they may have the same string representation and GraphViz could treat them as the same node. The layout may assign both nodes a single location. See Issue #1568 If this occurs in your case, consider relabeling the nodes just for the layout computation using something similar to: H = nx.convert_node_labels_to_integers(G, label_attribute='node_label') H_layout = nx.nx_pydot.pydot_layout(G, prog='dot') G_layout = {H.nodes[n]['node_label']: p for n, p in H_layout.items()} """ import pydot P = to_pydot(G) if root is not None: P.set("root", make_str(root)) # List of low-level bytes comprising a string in the dot language converted # from the passed graph with the passed external GraphViz command. D_bytes = P.create_dot(prog=prog) # Unique string decoded from these bytes with the preferred locale encoding D = str(D_bytes, encoding=getpreferredencoding()) if D == "": # no data returned print("Graphviz layout with %s failed" % (prog)) print() print("To debug what happened try:") print("P = nx.nx_pydot.to_pydot(G)") print("P.write_dot(\"file.dot\")") print("And then run %s on file.dot" % (prog)) return # List of one or more "pydot.Dot" instances deserialized from this string. Q_list = pydot.graph_from_dot_data(D) assert len(Q_list) == 1 # The first and only such instance, as guaranteed by the above assertion. Q = Q_list[0] node_pos = {} for n in G.nodes(): pydot_node = pydot.Node(make_str(n)).get_name() node = Q.get_node(pydot_node) if isinstance(node, list): node = node[0] pos = node.get_pos()[1:-1] # strip leading and trailing double quotes if pos is not None: xx, yy = pos.split(",") node_pos[n] = (float(xx), float(yy)) return node_pos
def to_pydot(N): """Returns a pydot graph from a NetworkX graph N. Parameters ---------- N : NetworkX graph A graph created with NetworkX Examples -------- >>> K5 = nx.complete_graph(5) >>> P = nx.nx_pydot.to_pydot(K5) Notes ----- """ pydot = _import_pydot() # set Graphviz graph type if N.is_directed(): graph_type = 'digraph' else: graph_type = 'graph' strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph() name = N.name graph_defaults = N.graph.get('graph', {}) if name == '': P = pydot.Dot('', graph_type=graph_type, strict=strict, **graph_defaults) else: P = pydot.Dot('"%s"' % name, graph_type=graph_type, strict=strict, **graph_defaults) try: P.set_node_defaults(**N.graph['node']) except KeyError: pass try: P.set_edge_defaults(**N.graph['edge']) except KeyError: pass for n, nodedata in N.nodes(data=True): str_nodedata = dict((k, make_str(v)) for k, v in nodedata.items()) p = pydot.Node(make_str(n), **str_nodedata) P.add_node(p) if N.is_multigraph(): for u, v, key, edgedata in N.edges(data=True, keys=True): str_edgedata = dict( (k, make_str(v)) for k, v in edgedata.items() if k != 'key') edge = pydot.Edge(make_str(u), make_str(v), key=make_str(key), **str_edgedata) P.add_edge(edge) else: for u, v, edgedata in N.edges(data=True): str_edgedata = dict((k, make_str(v)) for k, v in edgedata.items()) edge = pydot.Edge(make_str(u), make_str(v), **str_edgedata) P.add_edge(edge) return P
def test_make_str_with_unicode(): x = "qualité" y = make_str(x) assert isinstance(y, str) assert len(y) == 7
def parse_pajek(lines): """Parse Pajek format graph from string or iterable. Parameters ---------- lines : string or iterable Data in Pajek format. Returns ------- G : NetworkX graph See Also -------- read_pajek() """ import shlex # multigraph=False if is_string_like(lines): lines = iter(lines.split('\n')) lines = iter([line.rstrip('\n') for line in lines]) G = nx.MultiDiGraph() # are multiedges allowed in Pajek? assume yes while lines: try: l = next(lines) except: #EOF break if l.lower().startswith("*network"): try: label, name = l.split() except ValueError: # Line was not of the form: *network NAME pass else: G.graph['name'] = name elif l.lower().startswith("*vertices"): nodelabels = {} l, nnodes = l.split() for i in range(int(nnodes)): l = next(lines) try: splitline = [ x.decode('utf-8') for x in shlex.split(make_str(l).encode('utf-8')) ] except AttributeError: splitline = shlex.split(str(l)) id, label = splitline[0:2] G.add_node(id) nodelabels[id] = label G.node[id] = {'label': label} try: x, y, shape = splitline[2:5] G.node[id].update({ 'x': float(x), 'y': float(y), 'shape': shape }) except: pass extra_attr = zip(splitline[5::2], splitline[6::2]) G.node[id].update(extra_attr) try: extra_attr = zip(['color'], [splitline[-3]]) G.node[id].update(extra_attr) except: pass elif l.lower().startswith("*edges") or l.lower().startswith("*arcs"): if l.lower().startswith("*edge"): # switch from multidigraph to multigraph G = nx.MultiGraph(G) if l.lower().startswith("*arcs"): # switch to directed with multiple arcs for each existing edge G = G.to_directed() for l in lines: try: splitline = [ x.decode('utf-8') for x in shlex.split(make_str(l).encode('utf-8')) ] except AttributeError: splitline = shlex.split(str(l)) if len(splitline) < 2: continue ui, vi = splitline[0:2] u = ui v = vi #u=nodelabels.get(ui,ui) #v=nodelabels.get(vi,vi) # parse the data attached to this edge and put in a dictionary edge_data = {} try: # there should always be a single value on the edge? w = splitline[2:3] edge_data.update({'weight': float(w[0])}) except: pass # if there isn't, just assign a 1 # edge_data.update({'value':1}) extra_attr = zip(splitline[3::2], splitline[4::2]) edge_data.update(extra_attr) # if G.has_edge(u,v): # multigraph=True G.add_edge(u, v, **edge_data) return G
def node_link_graph(data, directed=False, multigraph=True, attrs=None): """Return graph from node-link data format. Parameters ---------- data : dict node-link formatted graph data directed : bool If True, and direction not specified in data, return a directed graph. multigraph : bool If True, and multigraph not specified in data, return a multigraph. attrs : dict A dictionary that contains five keys 'source', 'target', 'name', 'key' and 'link'. The corresponding values provide the attribute names for storing NetworkX-internal graph data. Default value: dict(source='source', target='target', name='id', key='key', link='links') Returns ------- G : NetworkX graph A NetworkX graph object Examples -------- >>> from networkx.readwrite import json_graph >>> G = nx.Graph([('A', 'B')]) >>> data = json_graph.node_link_data(G) >>> H = json_graph.node_link_graph(data) Notes ----- Attribute 'key' is only used for multigraphs. See Also -------- node_link_data, adjacency_data, tree_data """ # Allow 'attrs' to keep default values. if attrs is None: attrs = _attrs else: attrs.update(dict((k, v) for k, v in _attrs.items() if k not in attrs)) multigraph = data.get('multigraph', multigraph) directed = data.get('directed', directed) if multigraph: graph = nx.MultiGraph() else: graph = nx.Graph() if directed: graph = graph.to_directed() name = attrs['name'] source = attrs['source'] target = attrs['target'] links = attrs['link'] # Allow 'key' to be omitted from attrs if the graph is not a multigraph. key = None if not multigraph else attrs['key'] graph.graph = data.get('graph', {}) c = count() for d in data['nodes']: node = to_tuple(d.get(name, next(c))) nodedata = dict((make_str(k), v) for k, v in d.items() if k != name) graph.add_node(node, **nodedata) for d in data[links]: src = tuple(d[source]) if isinstance(d[source], list) else d[source] tgt = tuple(d[target]) if isinstance(d[target], list) else d[target] if not multigraph: edgedata = dict((make_str(k), v) for k, v in d.items() if k != source and k != target) graph.add_edge(src, tgt, **edgedata) else: ky = d.get(key, None) edgedata = dict((make_str(k), v) for k, v in d.items() if k != source and k != target and k != key) graph.add_edge(src, tgt, ky, **edgedata) return graph