1import warnings
2from itertools import count
3
4import networkx as nx
5
6__all__ = ["node_link_data", "node_link_graph"]
7
8
9def _to_tuple(x):
10 """Converts lists to tuples, including nested lists.
11
12 All other non-list inputs are passed through unmodified. This function is
13 intended to be used to convert potentially nested lists from json files
14 into valid nodes.
15
16 Examples
17 --------
18 >>> _to_tuple([1, 2, [3, 4]])
19 (1, 2, (3, 4))
20 """
21 if not isinstance(x, tuple | list):
22 return x
23 return tuple(map(_to_tuple, x))
24
25
26def node_link_data(
27 G,
28 *,
29 source="source",
30 target="target",
31 name="id",
32 key="key",
33 edges="edges",
34 nodes="nodes",
35):
36 """Returns data in node-link format that is suitable for JSON serialization
37 and use in JavaScript documents.
38
39 Parameters
40 ----------
41 G : NetworkX graph
42 source : string
43 A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
44 target : string
45 A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
46 name : string
47 A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
48 key : string
49 A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
50 edges : string
51 A string that provides the 'edges' attribute name for storing NetworkX-internal graph data.
52 nodes : string
53 A string that provides the 'nodes' attribute name for storing NetworkX-internal graph data.
54
55 Returns
56 -------
57 data : dict
58 A dictionary with node-link formatted data.
59
60 Raises
61 ------
62 NetworkXError
63 If the values of 'source', 'target' and 'key' are not unique.
64
65 Examples
66 --------
67 >>> from pprint import pprint
68 >>> G = nx.Graph([("A", "B")])
69 >>> data1 = nx.node_link_data(G)
70 >>> pprint(data1)
71 {'directed': False,
72 'edges': [{'source': 'A', 'target': 'B'}],
73 'graph': {},
74 'multigraph': False,
75 'nodes': [{'id': 'A'}, {'id': 'B'}]}
76
77 To serialize with JSON
78
79 >>> import json
80 >>> s1 = json.dumps(data1)
81 >>> pprint(s1)
82 ('{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, '
83 '{"id": "B"}], "edges": [{"source": "A", "target": "B"}]}')
84
85
86 A graph can also be serialized by passing `node_link_data` as an encoder function.
87
88 >>> s1 = json.dumps(G, default=nx.node_link_data)
89 >>> pprint(s1)
90 ('{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": "A"}, '
91 '{"id": "B"}], "edges": [{"source": "A", "target": "B"}]}')
92
93 The attribute names for storing NetworkX-internal graph data can
94 be specified as keyword options.
95
96 >>> H = nx.gn_graph(2)
97 >>> data2 = nx.node_link_data(
98 ... H, edges="links", source="from", target="to", nodes="vertices"
99 ... )
100 >>> pprint(data2)
101 {'directed': True,
102 'graph': {},
103 'links': [{'from': 1, 'to': 0}],
104 'multigraph': False,
105 'vertices': [{'id': 0}, {'id': 1}]}
106
107 Notes
108 -----
109 Graph, node, and edge attributes are stored in this format. Note that
110 attribute keys will be converted to strings in order to comply with JSON.
111
112 Attribute 'key' is only used for multigraphs.
113
114 To use `node_link_data` in conjunction with `node_link_graph`,
115 the keyword names for the attributes must match.
116
117 See Also
118 --------
119 node_link_graph, adjacency_data, tree_data
120 """
121 multigraph = G.is_multigraph()
122
123 # Allow 'key' to be omitted from attrs if the graph is not a multigraph.
124 key = None if not multigraph else key
125 if len({source, target, key}) < 3:
126 raise nx.NetworkXError("Attribute names are not unique.")
127 data = {
128 "directed": G.is_directed(),
129 "multigraph": multigraph,
130 "graph": G.graph,
131 nodes: [{**G.nodes[n], name: n} for n in G],
132 }
133 if multigraph:
134 data[edges] = [
135 {**d, source: u, target: v, key: k}
136 for u, v, k, d in G.edges(keys=True, data=True)
137 ]
138 else:
139 data[edges] = [{**d, source: u, target: v} for u, v, d in G.edges(data=True)]
140 return data
141
142
143@nx._dispatchable(graphs=None, returns_graph=True)
144def node_link_graph(
145 data,
146 directed=False,
147 multigraph=True,
148 *,
149 source="source",
150 target="target",
151 name="id",
152 key="key",
153 edges="edges",
154 nodes="nodes",
155):
156 """Returns graph from node-link data format.
157
158 Useful for de-serialization from JSON.
159
160 Parameters
161 ----------
162 data : dict
163 node-link formatted graph data
164
165 directed : bool
166 If True, and direction not specified in data, return a directed graph.
167
168 multigraph : bool
169 If True, and multigraph not specified in data, return a multigraph.
170
171 source : string
172 A string that provides the 'source' attribute name for storing NetworkX-internal graph data.
173 target : string
174 A string that provides the 'target' attribute name for storing NetworkX-internal graph data.
175 name : string
176 A string that provides the 'name' attribute name for storing NetworkX-internal graph data.
177 key : string
178 A string that provides the 'key' attribute name for storing NetworkX-internal graph data.
179 edges : string
180 A string that provides the 'edges' attribute name for storing NetworkX-internal graph data.
181 nodes : string
182 A string that provides the 'nodes' attribute name for storing NetworkX-internal graph data.
183
184 Returns
185 -------
186 G : NetworkX graph
187 A NetworkX graph object
188
189 Examples
190 --------
191
192 Create data in node-link format by converting a graph.
193
194 >>> from pprint import pprint
195 >>> G = nx.Graph([("A", "B")])
196 >>> data = nx.node_link_data(G)
197 >>> pprint(data)
198 {'directed': False,
199 'edges': [{'source': 'A', 'target': 'B'}],
200 'graph': {},
201 'multigraph': False,
202 'nodes': [{'id': 'A'}, {'id': 'B'}]}
203
204 Revert data in node-link format to a graph.
205
206 >>> H = nx.node_link_graph(data)
207 >>> print(H.edges)
208 [('A', 'B')]
209
210 To serialize and deserialize a graph with JSON,
211
212 >>> import json
213 >>> d = json.dumps(nx.node_link_data(G))
214 >>> H = nx.node_link_graph(json.loads(d))
215 >>> print(G.edges, H.edges)
216 [('A', 'B')] [('A', 'B')]
217
218
219 Notes
220 -----
221 Attribute 'key' is only used for multigraphs.
222
223 To use `node_link_data` in conjunction with `node_link_graph`,
224 the keyword names for the attributes must match.
225
226 See Also
227 --------
228 node_link_data, adjacency_data, tree_data
229 """
230 multigraph = data.get("multigraph", multigraph)
231 directed = data.get("directed", directed)
232 if multigraph:
233 graph = nx.MultiGraph()
234 else:
235 graph = nx.Graph()
236 if directed:
237 graph = graph.to_directed()
238
239 # Allow 'key' to be omitted from attrs if the graph is not a multigraph.
240 key = None if not multigraph else key
241 graph.graph = data.get("graph", {})
242 c = count()
243 for d in data[nodes]:
244 node = _to_tuple(d.get(name, next(c)))
245 nodedata = {str(k): v for k, v in d.items() if k != name}
246 graph.add_node(node, **nodedata)
247 for d in data[edges]:
248 src = tuple(d[source]) if isinstance(d[source], list) else d[source]
249 tgt = tuple(d[target]) if isinstance(d[target], list) else d[target]
250 if not multigraph:
251 edgedata = {str(k): v for k, v in d.items() if k != source and k != target}
252 graph.add_edge(src, tgt, **edgedata)
253 else:
254 ky = d.get(key, None)
255 edgedata = {
256 str(k): v
257 for k, v in d.items()
258 if k != source and k != target and k != key
259 }
260 graph.add_edge(src, tgt, ky, **edgedata)
261 return graph