We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Feature Request] ‘from_networkx’ function for a fixed layout
# Summary
Currently (ver 0.13.0) Bokeh, `from_networkx` function always requests a layout function. 
Personally I think that it is more useful to be able to convert a graph structure using **a fixed layout** as well as layout functions.

# Detail
## Use Cases
I think that this feature is particularly useful in the following scenes.

(A) The case where **the user wants to specify a fixed layout that customizes the position by oneself** instead of using a layout function.
- For example, a transportation network associating nodes with geographical positions.

(B) The case where **the user wants to calculate a layout before calling `from_networkx`** since the calculation time is too long.	
 - Currently, layout is calculated every time `from_networkx` is called. Therefore, it takes time to try to recreate `GraphRenderer` many times. It will become a large problem in the case of a large graph.

I think that it’s a general case to use a fixed layout.

## Workaround
I currently handle a fixed layout by the following methods. But they are **NOT** fundamental solutions.

1. Specify a layout function (eg `nx.spring_layout function`, etc.) as **a dummy** and then update the `layout_provider` attribute with a fixed layout.
    -  [Code Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/networkx2bokeh_layout_for_custom_position.ipynb)
2. Prepare a function that returns a fixed layout (dict) and pass it in the argument `layout_function`.
    - [Code Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/use_function_return_layout.ipynb#Workaround)

### Problem
Workaround (1) solves use case (A), but it  does **NOT** solve use case (B).
It seems that workaround (2) can solve both use case (A) and (B), **BUT** I think that it’s a bother.
Therefore I hope for a fundamental solution.

# Expected Result
1. Be able to specify a fixed layout in `from_networkx`
2. Or, be able to use `from_networkx` without a layout function

# Solution
I imagine there are several methods of implementation as below, but I don't really have a strong opinion.
If Bokeh has an API design policy and future vision, I would like to follow them.

## Before
https://github.com/bokeh/bokeh/blob/e46ef320ff33be0b64be9d8fbd2eea2ad86aa24c/bokeh/models/graphs.py#L35-L109

## After
### Approach 1. Add a new function for graphs with a fixed layout
- Add a new function apart from the existing `from_networkx`.
- The difference from `from_networkx` is that it is passed a fixed layout (dict) as an argument.

[Code & Usage Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/draft_from_networkx_for_fixed_layout.ipynb#Approach-1.-Add-a-new-function-for-graphs-with-fixed-layout)

### Approach 2. Update `from_networkx` to handle both fixed layout and layout function
- Rename `layout_function` to `layout` and allow to receive both dictionary and function.
- If `layout`  is a function, execute the function and calculate the layout. Otherwise, set it directly to `graph_renderer.layout_provider` in `from_networkx`.
- It is **NOT** backward compatible with 0.13.0 since the argument name changes.

[Code & Usage Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/draft_from_networkx_for_fixed_layout.ipynb#Approach-2.-Update-from_networkx-to-handle-both-fixed-layout-and-layout-function)

### Approach 3. Update `from_networkx` so that it can be called without a layout function
- Make `layout_function` an optional argument.
- The user manually sets a fixed layout after calling `from_networkx`. 

[Code & Usage Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/draft_from_networkx_for_fixed_layout.ipynb#Approach-3.-Update-from_networkx-so-that--it-can-be-called-without-a-layout-function)

## Or Other Approaches
Other people may have better ideas.

I'm often bothered by this problem, so I'm interested in sending a PR.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bokeh/models/graphs.py`
Content:
```
1 from ..core.has_props import abstract
2 from ..core.properties import Any, Dict, Either, Int, Seq, String
3 from ..model import Model
4 from ..models.sources import ColumnDataSource
5 
6 
7 @abstract
8 class LayoutProvider(Model):
9     '''
10 
11     '''
12 
13     pass
14 
15 
16 class StaticLayoutProvider(LayoutProvider):
17     '''
18 
19     '''
20 
21     graph_layout = Dict(Either(String, Int), Seq(Any), default={}, help="""
22     The coordinates of the graph nodes in cartesian space. The dictionary
23     keys correspond to a node index and the values are a two element sequence
24     containing the x and y coordinates of the node.
25 
26     .. code-block:: python
27 
28         {
29             0 : [0.5, 0.5],
30             1 : [1.0, 0.86],
31             2 : [0.86, 1],
32         }
33     """)
34 
35 def from_networkx(graph, layout_function, **kwargs):
36         '''
37         Generate a GraphRenderer from a networkx.Graph object and networkx
38         layout function. Any keyword arguments will be passed to the
39         layout function.
40 
41         Args:
42             graph (networkx.Graph) : a networkx graph to render
43             layout_function (function) : a networkx layout function
44 
45         Returns:
46             instance (GraphRenderer)
47 
48         .. warning::
49             Only two dimensional layouts are currently supported.
50 
51         .. warning::
52             Node attributes labeled 'index' and edge attributes labeled 'start' or 'end' are ignored.
53             If you want to convert these attributes, please re-label them to other names.
54 
55         '''
56 
57         # inline import to prevent circular imports
58         from ..models.renderers import GraphRenderer
59         from ..models.graphs import StaticLayoutProvider
60 
61         # Handles nx 1.x vs 2.x data structure change
62         # Convert node attributes
63         node_dict = dict()
64         node_attr_keys = [attr_key for node in list(graph.nodes(data=True))
65                           for attr_key in node[1].keys()]
66         node_attr_keys = list(set(node_attr_keys))
67 
68         for attr_key in node_attr_keys:
69             node_dict[attr_key] = [node_attr[attr_key] if attr_key in node_attr.keys() else None
70                                    for _, node_attr
71                                    in graph.nodes(data=True)]
72 
73         if 'index' in node_attr_keys:
74             from warnings import warn
75             warn("Converting node attributes labeled 'index' are skipped. "
76                  "If you want to convert these attributes, please re-label with other names.")
77 
78         node_dict['index'] = list(graph.nodes())
79 
80         # Convert edge attributes
81         edge_dict = dict()
82         edge_attr_keys = [attr_key for edge in graph.edges(data=True)
83                           for attr_key in edge[2].keys()]
84         edge_attr_keys = list(set(edge_attr_keys))
85 
86         for attr_key in edge_attr_keys:
87             edge_dict[attr_key] = [edge_attr[attr_key] if attr_key in edge_attr.keys() else None
88                                    for _, _, edge_attr
89                                    in graph.edges(data=True)]
90 
91         if 'start' in edge_attr_keys or 'end' in edge_attr_keys:
92             from warnings import warn
93             warn("Converting edge attributes labeled 'start' or 'end' are skipped. "
94                  "If you want to convert these attributes, please re-label them with other names.")
95 
96         edge_dict['start'] = [x[0] for x in graph.edges()]
97         edge_dict['end'] = [x[1] for x in graph.edges()]
98 
99         node_source = ColumnDataSource(data=node_dict)
100         edge_source = ColumnDataSource(data=edge_dict)
101 
102         graph_renderer = GraphRenderer()
103         graph_renderer.node_renderer.data_source.data = node_source.data
104         graph_renderer.edge_renderer.data_source.data = edge_source.data
105 
106         graph_layout = layout_function(graph, **kwargs)
107         graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)
108 
109         return graph_renderer
110 
111 
112 @abstract
113 class GraphHitTestPolicy(Model):
114     '''
115 
116     '''
117 
118     pass
119 
120 
121 class NodesOnly(GraphHitTestPolicy):
122     '''
123     With the NodesOnly policy, only graph nodes are able to be selected and
124     inspected. There is no selection or inspection of graph edges.
125 
126     '''
127 
128     pass
129 
130 class NodesAndLinkedEdges(GraphHitTestPolicy):
131     '''
132     With the NodesAndLinkedEdges policy, inspection or selection of graph
133     nodes will result in the inspection or selection of the node and of the
134     linked graph edges. There is no direct selection or inspection of graph
135     edges.
136 
137     '''
138 
139     pass
140 
141 class EdgesAndLinkedNodes(GraphHitTestPolicy):
142     '''
143     With the EdgesAndLinkedNodes policy, inspection or selection of graph
144     edges will result in the inspection or selection of the edge and of the
145     linked graph nodes. There is no direct selection or inspection of graph
146     nodes.
147 
148     '''
149 
150     pass
151
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bokeh/models/graphs.py b/bokeh/models/graphs.py
--- a/bokeh/models/graphs.py
+++ b/bokeh/models/graphs.py
@@ -40,7 +40,8 @@
 
         Args:
             graph (networkx.Graph) : a networkx graph to render
-            layout_function (function) : a networkx layout function
+            layout_function (function or dict) : a networkx layout function or mapping of node keys to positions.
+            The position is a two element sequence containing the x and y coordinate.
 
         Returns:
             instance (GraphRenderer)
@@ -103,7 +104,17 @@
         graph_renderer.node_renderer.data_source.data = node_source.data
         graph_renderer.edge_renderer.data_source.data = edge_source.data
 
-        graph_layout = layout_function(graph, **kwargs)
+        if callable(layout_function):
+            graph_layout = layout_function(graph, **kwargs)
+        else:
+            graph_layout = layout_function
+
+            node_keys = graph_renderer.node_renderer.data_source.data['index']
+            if set(node_keys) != set(layout_function.keys()):
+                from warnings import warn
+                warn("Node keys in 'layout_function' don't match node keys in the graph. "
+                     "These nodes may not be displayed correctly.")
+
         graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)
 
         return graph_renderer

{"golden_diff": "diff --git a/bokeh/models/graphs.py b/bokeh/models/graphs.py\n--- a/bokeh/models/graphs.py\n+++ b/bokeh/models/graphs.py\n@@ -40,7 +40,8 @@\n \n         Args:\n             graph (networkx.Graph) : a networkx graph to render\n-            layout_function (function) : a networkx layout function\n+            layout_function (function or dict) : a networkx layout function or mapping of node keys to positions.\n+            The position is a two element sequence containing the x and y coordinate.\n \n         Returns:\n             instance (GraphRenderer)\n@@ -103,7 +104,17 @@\n         graph_renderer.node_renderer.data_source.data = node_source.data\n         graph_renderer.edge_renderer.data_source.data = edge_source.data\n \n-        graph_layout = layout_function(graph, **kwargs)\n+        if callable(layout_function):\n+            graph_layout = layout_function(graph, **kwargs)\n+        else:\n+            graph_layout = layout_function\n+\n+            node_keys = graph_renderer.node_renderer.data_source.data['index']\n+            if set(node_keys) != set(layout_function.keys()):\n+                from warnings import warn\n+                warn(\"Node keys in 'layout_function' don't match node keys in the graph. \"\n+                     \"These nodes may not be displayed correctly.\")\n+\n         graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)\n \n         return graph_renderer\n", "issue": "[Feature Request] \u2018from_networkx\u2019 function for a fixed layout\n# Summary\r\nCurrently\u00a0(ver 0.13.0)\u00a0Bokeh,\u00a0`from_networkx`\u00a0function always requests a layout function. \r\nPersonally I think that it is more useful to be able to convert a graph structure using **a fixed layout** as well as layout functions.\r\n\r\n# Detail\r\n## Use Cases\r\nI think that this feature is particularly useful in the following scenes.\r\n\r\n(A) The case where **the user wants to specify a fixed layout that customizes the position by oneself** instead of using a layout function.\r\n- For example, a transportation network associating nodes with geographical positions.\r\n\r\n(B) The case where **the user wants to calculate a layout before calling `from_networkx`** since the calculation time is too long.\t\r\n - Currently, layout is calculated every time `from_networkx` is called. Therefore, it takes time to try to recreate `GraphRenderer` many times. It will become a large problem in the case of a large graph.\r\n\r\nI think that it\u2019s a general case to use a fixed layout.\r\n\r\n## Workaround\r\nI currently handle a fixed layout by the following methods. But they are **NOT** fundamental solutions.\r\n\r\n1. Specify a layout function\u00a0(eg `nx.spring_layout\u00a0function`,\u00a0etc.) as **a dummy**\u00a0and then update the `layout_provider` attribute with a fixed layout.\r\n    -  [Code Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/networkx2bokeh_layout_for_custom_position.ipynb)\r\n2. Prepare a function that returns a fixed layout (dict) and pass it in the argument `layout_function`.\r\n    - [Code Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/use_function_return_layout.ipynb#Workaround)\r\n\r\n### Problem\r\nWorkaround (1) solves use case (A), but it  does **NOT** solve use case (B).\r\nIt seems that workaround (2) can solve both use case (A) and (B), **BUT** I think that it\u2019s a bother.\r\nTherefore I hope for a fundamental solution.\r\n\r\n# Expected Result\r\n1. Be able to specify a fixed layout in `from_networkx`\r\n2. Or, be able to use `from_networkx` without a layout function\r\n\r\n# Solution\r\nI imagine there are several methods of implementation as below, but I don't really have a strong opinion.\r\nIf Bokeh has an API design policy and future vision, I would like to follow them.\r\n\r\n## Before\r\nhttps://github.com/bokeh/bokeh/blob/e46ef320ff33be0b64be9d8fbd2eea2ad86aa24c/bokeh/models/graphs.py#L35-L109\r\n\r\n## After\r\n### Approach 1. Add a new function for graphs with a fixed layout\r\n- Add a new function apart from the existing `from_networkx`.\r\n- The difference from `from_networkx` is that it is passed a fixed layout (dict) as an argument.\r\n\r\n[Code & Usage Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/draft_from_networkx_for_fixed_layout.ipynb#Approach-1.-Add-a-new-function-for-graphs-with-fixed-layout)\r\n\r\n### Approach 2. Update `from_networkx` to handle both fixed layout and layout function\r\n- Rename `layout_function` to `layout` and allow to receive both dictionary and function.\r\n- If `layout`  is a function, execute the function and calculate the layout. Otherwise, set it directly to `graph_renderer.layout_provider` in `from_networkx`.\r\n- It is **NOT** backward compatible with 0.13.0 since the argument name changes.\r\n\r\n[Code & Usage Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/draft_from_networkx_for_fixed_layout.ipynb#Approach-2.-Update-from_networkx-to-handle-both-fixed-layout-and-layout-function)\r\n\r\n### Approach 3. Update `from_networkx` so that it can be called without a layout function\r\n- Make `layout_function` an optional argument.\r\n- The user manually sets a fixed layout after calling `from_networkx`. \r\n\r\n[Code & Usage Example](http://nbviewer.jupyter.org/github/komo-fr/networkx2bokeh_note/blob/master/notebooks/draft_from_networkx_for_fixed_layout.ipynb#Approach-3.-Update-from_networkx-so-that--it-can-be-called-without-a-layout-function)\r\n\r\n## Or Other Approaches\r\nOther people may have better ideas.\r\n\r\nI'm often bothered by this problem, so I'm interested in sending a PR.\n", "before_files": [{"content": "from ..core.has_props import abstract\nfrom ..core.properties import Any, Dict, Either, Int, Seq, String\nfrom ..model import Model\nfrom ..models.sources import ColumnDataSource\n\n\n@abstract\nclass LayoutProvider(Model):\n    '''\n\n    '''\n\n    pass\n\n\nclass StaticLayoutProvider(LayoutProvider):\n    '''\n\n    '''\n\n    graph_layout = Dict(Either(String, Int), Seq(Any), default={}, help=\"\"\"\n    The coordinates of the graph nodes in cartesian space. The dictionary\n    keys correspond to a node index and the values are a two element sequence\n    containing the x and y coordinates of the node.\n\n    .. code-block:: python\n\n        {\n            0 : [0.5, 0.5],\n            1 : [1.0, 0.86],\n            2 : [0.86, 1],\n        }\n    \"\"\")\n\ndef from_networkx(graph, layout_function, **kwargs):\n        '''\n        Generate a GraphRenderer from a networkx.Graph object and networkx\n        layout function. Any keyword arguments will be passed to the\n        layout function.\n\n        Args:\n            graph (networkx.Graph) : a networkx graph to render\n            layout_function (function) : a networkx layout function\n\n        Returns:\n            instance (GraphRenderer)\n\n        .. warning::\n            Only two dimensional layouts are currently supported.\n\n        .. warning::\n            Node attributes labeled 'index' and edge attributes labeled 'start' or 'end' are ignored.\n            If you want to convert these attributes, please re-label them to other names.\n\n        '''\n\n        # inline import to prevent circular imports\n        from ..models.renderers import GraphRenderer\n        from ..models.graphs import StaticLayoutProvider\n\n        # Handles nx 1.x vs 2.x data structure change\n        # Convert node attributes\n        node_dict = dict()\n        node_attr_keys = [attr_key for node in list(graph.nodes(data=True))\n                          for attr_key in node[1].keys()]\n        node_attr_keys = list(set(node_attr_keys))\n\n        for attr_key in node_attr_keys:\n            node_dict[attr_key] = [node_attr[attr_key] if attr_key in node_attr.keys() else None\n                                   for _, node_attr\n                                   in graph.nodes(data=True)]\n\n        if 'index' in node_attr_keys:\n            from warnings import warn\n            warn(\"Converting node attributes labeled 'index' are skipped. \"\n                 \"If you want to convert these attributes, please re-label with other names.\")\n\n        node_dict['index'] = list(graph.nodes())\n\n        # Convert edge attributes\n        edge_dict = dict()\n        edge_attr_keys = [attr_key for edge in graph.edges(data=True)\n                          for attr_key in edge[2].keys()]\n        edge_attr_keys = list(set(edge_attr_keys))\n\n        for attr_key in edge_attr_keys:\n            edge_dict[attr_key] = [edge_attr[attr_key] if attr_key in edge_attr.keys() else None\n                                   for _, _, edge_attr\n                                   in graph.edges(data=True)]\n\n        if 'start' in edge_attr_keys or 'end' in edge_attr_keys:\n            from warnings import warn\n            warn(\"Converting edge attributes labeled 'start' or 'end' are skipped. \"\n                 \"If you want to convert these attributes, please re-label them with other names.\")\n\n        edge_dict['start'] = [x[0] for x in graph.edges()]\n        edge_dict['end'] = [x[1] for x in graph.edges()]\n\n        node_source = ColumnDataSource(data=node_dict)\n        edge_source = ColumnDataSource(data=edge_dict)\n\n        graph_renderer = GraphRenderer()\n        graph_renderer.node_renderer.data_source.data = node_source.data\n        graph_renderer.edge_renderer.data_source.data = edge_source.data\n\n        graph_layout = layout_function(graph, **kwargs)\n        graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)\n\n        return graph_renderer\n\n\n@abstract\nclass GraphHitTestPolicy(Model):\n    '''\n\n    '''\n\n    pass\n\n\nclass NodesOnly(GraphHitTestPolicy):\n    '''\n    With the NodesOnly policy, only graph nodes are able to be selected and\n    inspected. There is no selection or inspection of graph edges.\n\n    '''\n\n    pass\n\nclass NodesAndLinkedEdges(GraphHitTestPolicy):\n    '''\n    With the NodesAndLinkedEdges policy, inspection or selection of graph\n    nodes will result in the inspection or selection of the node and of the\n    linked graph edges. There is no direct selection or inspection of graph\n    edges.\n\n    '''\n\n    pass\n\nclass EdgesAndLinkedNodes(GraphHitTestPolicy):\n    '''\n    With the EdgesAndLinkedNodes policy, inspection or selection of graph\n    edges will result in the inspection or selection of the edge and of the\n    linked graph nodes. There is no direct selection or inspection of graph\n    nodes.\n\n    '''\n\n    pass\n", "path": "bokeh/models/graphs.py"}], "after_files": [{"content": "from ..core.has_props import abstract\nfrom ..core.properties import Any, Dict, Either, Int, Seq, String\nfrom ..model import Model\nfrom ..models.sources import ColumnDataSource\n\n\n@abstract\nclass LayoutProvider(Model):\n    '''\n\n    '''\n\n    pass\n\n\nclass StaticLayoutProvider(LayoutProvider):\n    '''\n\n    '''\n\n    graph_layout = Dict(Either(String, Int), Seq(Any), default={}, help=\"\"\"\n    The coordinates of the graph nodes in cartesian space. The dictionary\n    keys correspond to a node index and the values are a two element sequence\n    containing the x and y coordinates of the node.\n\n    .. code-block:: python\n\n        {\n            0 : [0.5, 0.5],\n            1 : [1.0, 0.86],\n            2 : [0.86, 1],\n        }\n    \"\"\")\n\ndef from_networkx(graph, layout_function, **kwargs):\n        '''\n        Generate a GraphRenderer from a networkx.Graph object and networkx\n        layout function. Any keyword arguments will be passed to the\n        layout function.\n\n        Args:\n            graph (networkx.Graph) : a networkx graph to render\n            layout_function (function or dict) : a networkx layout function or mapping of node keys to positions.\n            The position is a two element sequence containing the x and y coordinate.\n\n        Returns:\n            instance (GraphRenderer)\n\n        .. warning::\n            Only two dimensional layouts are currently supported.\n\n        .. warning::\n            Node attributes labeled 'index' and edge attributes labeled 'start' or 'end' are ignored.\n            If you want to convert these attributes, please re-label them to other names.\n\n        '''\n\n        # inline import to prevent circular imports\n        from ..models.renderers import GraphRenderer\n        from ..models.graphs import StaticLayoutProvider\n\n        # Handles nx 1.x vs 2.x data structure change\n        # Convert node attributes\n        node_dict = dict()\n        node_attr_keys = [attr_key for node in list(graph.nodes(data=True))\n                          for attr_key in node[1].keys()]\n        node_attr_keys = list(set(node_attr_keys))\n\n        for attr_key in node_attr_keys:\n            node_dict[attr_key] = [node_attr[attr_key] if attr_key in node_attr.keys() else None\n                                   for _, node_attr\n                                   in graph.nodes(data=True)]\n\n        if 'index' in node_attr_keys:\n            from warnings import warn\n            warn(\"Converting node attributes labeled 'index' are skipped. \"\n                 \"If you want to convert these attributes, please re-label with other names.\")\n\n        node_dict['index'] = list(graph.nodes())\n\n        # Convert edge attributes\n        edge_dict = dict()\n        edge_attr_keys = [attr_key for edge in graph.edges(data=True)\n                          for attr_key in edge[2].keys()]\n        edge_attr_keys = list(set(edge_attr_keys))\n\n        for attr_key in edge_attr_keys:\n            edge_dict[attr_key] = [edge_attr[attr_key] if attr_key in edge_attr.keys() else None\n                                   for _, _, edge_attr\n                                   in graph.edges(data=True)]\n\n        if 'start' in edge_attr_keys or 'end' in edge_attr_keys:\n            from warnings import warn\n            warn(\"Converting edge attributes labeled 'start' or 'end' are skipped. \"\n                 \"If you want to convert these attributes, please re-label them with other names.\")\n\n        edge_dict['start'] = [x[0] for x in graph.edges()]\n        edge_dict['end'] = [x[1] for x in graph.edges()]\n\n        node_source = ColumnDataSource(data=node_dict)\n        edge_source = ColumnDataSource(data=edge_dict)\n\n        graph_renderer = GraphRenderer()\n        graph_renderer.node_renderer.data_source.data = node_source.data\n        graph_renderer.edge_renderer.data_source.data = edge_source.data\n\n        if callable(layout_function):\n            graph_layout = layout_function(graph, **kwargs)\n        else:\n            graph_layout = layout_function\n\n            node_keys = graph_renderer.node_renderer.data_source.data['index']\n            if set(node_keys) != set(layout_function.keys()):\n                from warnings import warn\n                warn(\"Node keys in 'layout_function' don't match node keys in the graph. \"\n                     \"These nodes may not be displayed correctly.\")\n\n        graph_renderer.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)\n\n        return graph_renderer\n\n\n@abstract\nclass GraphHitTestPolicy(Model):\n    '''\n\n    '''\n\n    pass\n\n\nclass NodesOnly(GraphHitTestPolicy):\n    '''\n    With the NodesOnly policy, only graph nodes are able to be selected and\n    inspected. There is no selection or inspection of graph edges.\n\n    '''\n\n    pass\n\nclass NodesAndLinkedEdges(GraphHitTestPolicy):\n    '''\n    With the NodesAndLinkedEdges policy, inspection or selection of graph\n    nodes will result in the inspection or selection of the node and of the\n    linked graph edges. There is no direct selection or inspection of graph\n    edges.\n\n    '''\n\n    pass\n\nclass EdgesAndLinkedNodes(GraphHitTestPolicy):\n    '''\n    With the EdgesAndLinkedNodes policy, inspection or selection of graph\n    edges will result in the inspection or selection of the edge and of the\n    linked graph nodes. There is no direct selection or inspection of graph\n    nodes.\n\n    '''\n\n    pass\n", "path": "bokeh/models/graphs.py"}]}