node_link.py 11 KB

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