网络图#
Bokeh 允许您创建网络图可视化并配置边和节点之间的交互。
边和节点渲染器#
GraphRenderer
模型为图节点和边维护单独的子 GlyphRenderers
。这允许您通过修改 GraphRenderer
的 node_renderer
属性来自定义节点。您可以用任何 XYGlyph 实例(例如 Rect 或 Ellipse 图形)替换默认的 Circle 节点图形。您也可以通过 edge_renderer
属性类似地修改边的样式属性。要使用边图形,请使用 multi_line
图形方法。
请注意这些子渲染器所属的数据源的以下要求
节点子渲染器的
ColumnDataSource
必须具有一个"index"
列,其中包含节点的唯一索引。边子渲染器的
ColumnDataSource
必须具有"start"
和"end"
列。这些列包含边起点和终点的节点索引。
您可以向这些源添加额外的元数据,以启用矢量化图形样式或使数据可用于回调或悬停工具提示。
以下代码片段
用 Ellipse 替换节点图形,
将标量值分配给 Ellipse 的
height
和width
属性,将调色板分配给 Ellipse 的
fill_color
属性,并将分配的值添加到节点数据源。
import math
from bokeh.plotting import figure, show
from bokeh.models import GraphRenderer, Ellipse, StaticLayoutProvider
from bokeh.palettes import Spectral8
# list the nodes and initialize a plot
N = 8
node_indices = list(range(N))
plot = figure(title="Graph layout demonstration", x_range=(-1.1,1.1),
y_range=(-1.1,1.1), tools="", toolbar_location=None)
graph = GraphRenderer()
# replace the node glyph with an ellipse
# set its height, width, and fill_color
graph.node_renderer.glyph = Ellipse(height=0.1, width=0.2,
fill_color="fill_color")
# assign a palette to ``fill_color`` and add it to the data source
graph.node_renderer.data_source.data = dict(
index=node_indices,
fill_color=Spectral8)
# add the rest of the assigned values to the data source
graph.edge_renderer.data_source.data = dict(
start=[0]*N,
end=node_indices)
Bokeh 带有一个内置的 LayoutProvider
模型,其中包含节点的 (x,y) 坐标字典。这允许您在笛卡尔空间中排列绘图元素。
以下代码片段使用此提供程序模型根据上述设置生成绘图。
# generate ellipses based on the ``node_indices`` list
circ = [i*2*math.pi/8 for i in node_indices]
# create lists of x- and y-coordinates
x = [math.cos(i) for i in circ]
y = [math.sin(i) for i in circ]
# convert the ``x`` and ``y`` lists into a dictionary of 2D-coordinates
# and assign each entry to a node on the ``node_indices`` list
graph_layout = dict(zip(node_indices, zip(x, y)))
# use the provider model to supply coourdinates to the graph
graph.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)
# render the graph
plot.renderers.append(graph)
# display the plot
show(plot)
将以上代码片段放在一起,将产生以下结果
显式路径#
默认情况下,StaticLayoutProvider
模型在提供的节点位置之间绘制直线路径。要设置显式边路径,请将路径列表提供给 edge_renderer
的 bokeh.models.sources.ColumnDataSource
数据源。 StaticLayoutProvider
模型在数据源的 "xs"
和 "ys"
列中查找这些路径。路径应与 "start"
和 "end"
点的顺序相同。在设置显式路径时要格外小心,因为没有验证来检查它们是否与节点位置匹配。
以下扩展了上述示例,并在节点之间绘制二次贝塞尔曲线
import math
from bokeh.models import Ellipse, GraphRenderer, StaticLayoutProvider
from bokeh.palettes import Spectral8
from bokeh.plotting import figure, show
N = 8
node_indices = list(range(N))
plot = figure(title="Graph Layout Demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
tools="", toolbar_location=None)
graph = GraphRenderer()
graph.node_renderer.data_source.add(node_indices, 'index')
graph.node_renderer.data_source.add(Spectral8, 'color')
graph.node_renderer.glyph = Ellipse(height=0.1, width=0.2, fill_color="color")
graph.edge_renderer.data_source.data = dict(
start=[0]*N,
end=node_indices)
# create a static layout
circ = [i*2*math.pi/8 for i in node_indices]
x = [math.cos(i) for i in circ]
y = [math.sin(i) for i in circ]
graph_layout = dict(zip(node_indices, zip(x, y)))
graph.layout_provider = StaticLayoutProvider(graph_layout=graph_layout)
# draw quadratic bezier paths
def bezier(start, end, control, steps):
return [(1-s)**2*start + 2*(1-s)*s*control + s**2*end for s in steps]
xs, ys = [], []
sx, sy = graph_layout[0]
steps = [i/100. for i in range(100)]
for node_index in node_indices:
ex, ey = graph_layout[node_index]
xs.append(bezier(sx, ex, 0, steps))
ys.append(bezier(sy, ey, 0, steps))
graph.edge_renderer.data_source.data['xs'] = xs
graph.edge_renderer.data_source.data['ys'] = ys
plot.renderers.append(graph)
show(plot)
NetworkX 集成#
Bokeh 集成了 NetworkX 包,因此您可以快速绘制网络图。 bokeh.plotting.from_networkx
方便方法接受 networkx.Graph
对象和 NetworkX 布局方法,并返回 GraphRenderer
模型的已配置实例。
以下是如何使用 networkx.spring_layout
方法布局 NetworkX 中内置的“Zachary 的空手道俱乐部图”数据集
import networkx as nx
from bokeh.palettes import Category20_20
from bokeh.plotting import figure, from_networkx, show
G = nx.desargues_graph() # always 20 nodes
p = figure(x_range=(-2, 2), y_range=(-2, 2),
x_axis_location=None, y_axis_location=None,
tools="hover", tooltips="index: @index")
p.grid.grid_line_color = None
graph = from_networkx(G, nx.spring_layout, scale=1.8, center=(0,0))
p.renderers.append(graph)
# Add some new columns to the node renderer data source
graph.node_renderer.data_source.data['index'] = list(range(len(G)))
graph.node_renderer.data_source.data['colors'] = Category20_20
graph.node_renderer.glyph.update(size=20, fill_color="colors")
show(p)
交互策略#
您可以通过设置 GraphRenderer
的 selection_policy
和 inspection_policy
属性来配置图的选择或检查行为。这些策略属性接受一个特殊的 GraphHitTestPolicy
模型实例。
例如,将 selection_policy
设置为 NodesAndLinkedEdges()
允许您选择一个节点及其所有关联边。类似地,将 inspection_policy
设置为 EdgesAndLinkedNodes()
允许您通过将鼠标悬停在边上使用 HoverTool 来检查边的 "start"
和 "end"
节点。 NodesAndAdjacentNodes()
允许您检查一个节点以及通过图边连接的所有其他节点。
您可以自定义边和节点子渲染器的 selection_glyph
、nonselection_glyph
和/或 hover_glyph
属性,以向您的图交互添加动态视觉元素。
以下是添加了节点和边交互的图的示例
import networkx as nx
from bokeh.models import (BoxSelectTool, HoverTool, MultiLine,
NodesAndLinkedEdges, Plot, Range1d, Scatter, TapTool)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx, show
G = nx.karate_club_graph()
plot = Plot(width=400, height=400, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"
plot.add_tools(HoverTool(tooltips=None), TapTool(), BoxSelectTool())
graph_renderer = from_networkx(G, nx.circular_layout, scale=1, center=(0, 0))
scatter_glyph = Scatter(size=15, fill_color=Spectral4[0])
graph_renderer.node_renderer.glyph = scatter_glyph
graph_renderer.node_renderer.selection_glyph = scatter_glyph.clone(fill_color=Spectral4[2])
graph_renderer.node_renderer.hover_glyph = scatter_glyph.clone(fill_color=Spectral4[1])
ml_glyph = MultiLine(line_color="#CCCCCC", line_alpha=0.8, line_width=5)
graph_renderer.edge_renderer.glyph = ml_glyph
graph_renderer.edge_renderer.selection_glyph = ml_glyph.clone(line_color=Spectral4[2], line_alpha=1)
graph_renderer.edge_renderer.hover_glyph = ml_glyph.clone(line_color=Spectral4[1], line_width=1)
graph_renderer.selection_policy = NodesAndLinkedEdges()
graph_renderer.inspection_policy = NodesAndLinkedEdges()
plot.renderers.append(graph_renderer)
show(plot)
import networkx as nx
from bokeh.models import (BoxSelectTool, EdgesAndLinkedNodes, HoverTool,
MultiLine, Plot, Range1d, Scatter, TapTool)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx, show
G = nx.karate_club_graph()
plot = Plot(width=400, height=400, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"
plot.add_tools(HoverTool(tooltips=None), TapTool(), BoxSelectTool())
graph_renderer = from_networkx(G, nx.circular_layout, scale=1, center=(0, 0))
scatter_glyph = Scatter(size=15, fill_color=Spectral4[0])
graph_renderer.node_renderer.glyph = scatter_glyph
graph_renderer.node_renderer.selection_glyph = scatter_glyph.clone(fill_color=Spectral4[2])
graph_renderer.node_renderer.hover_glyph = scatter_glyph.clone(fill_color=Spectral4[1])
ml_glyph = MultiLine(line_color="#CCCCCC", line_alpha=0.8, line_width=5)
graph_renderer.edge_renderer.glyph = ml_glyph
graph_renderer.edge_renderer.selection_glyph = ml_glyph.clone(line_color=Spectral4[2], line_alpha=1)
graph_renderer.edge_renderer.hover_glyph = ml_glyph.clone(line_color=Spectral4[1], line_width=1)
graph_renderer.selection_policy = EdgesAndLinkedNodes()
graph_renderer.inspection_policy = EdgesAndLinkedNodes()
plot.renderers.append(graph_renderer)
show(plot)
import networkx as nx
from bokeh.models import (BoxSelectTool, HoverTool, MultiLine,
NodesAndAdjacentNodes, Plot, Range1d, Scatter, TapTool)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx, show
G = nx.karate_club_graph()
plot = Plot(width=400, height=400, x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"
plot.add_tools(HoverTool(tooltips=None), TapTool(), BoxSelectTool())
graph_renderer = from_networkx(G, nx.circular_layout, scale=1, center=(0, 0))
scatter_glyph = Scatter(size=15, fill_color=Spectral4[0])
graph_renderer.node_renderer.glyph = scatter_glyph
graph_renderer.node_renderer.selection_glyph = scatter_glyph.clone(fill_color=Spectral4[2])
graph_renderer.node_renderer.hover_glyph = scatter_glyph.clone(fill_color=Spectral4[1])
ml_glyph = MultiLine(line_color="#CCCCCC", line_alpha=0.8, line_width=5)
graph_renderer.edge_renderer.glyph = ml_glyph
graph_renderer.edge_renderer.selection_glyph = ml_glyph.clone(line_color=Spectral4[2], line_alpha=1)
graph_renderer.edge_renderer.hover_glyph = ml_glyph.clone(line_color=Spectral4[1], line_width=1)
graph_renderer.selection_policy = NodesAndAdjacentNodes()
graph_renderer.inspection_policy = NodesAndAdjacentNodes()
plot.renderers.append(graph_renderer)
show(plot)
节点和边属性#
from_networkx
方法转换 NetworkX 包的节点和边属性,以用于 GraphRenderer
模型的 node_renderer
和 edge_renderer
。
例如,“Zachary 的空手道俱乐部图”数据集具有一个名为“club”的节点属性。您可以使用 from_networkx
方法转换的节点属性来悬停此信息。您还可以使用节点和边属性作为颜色信息。
以下是一个悬停节点属性并使用边属性更改颜色的图的示例
import networkx as nx
from bokeh.models import MultiLine, Scatter
from bokeh.plotting import figure, from_networkx, show
G = nx.karate_club_graph()
SAME_CLUB_COLOR, DIFFERENT_CLUB_COLOR = "darkgrey", "red"
edge_attrs = {}
for start_node, end_node, _ in G.edges(data=True):
edge_color = SAME_CLUB_COLOR if G.nodes[start_node]["club"] == G.nodes[end_node]["club"] else DIFFERENT_CLUB_COLOR
edge_attrs[(start_node, end_node)] = edge_color
nx.set_edge_attributes(G, edge_attrs, "edge_color")
plot = figure(width=400, height=400, x_range=(-1.2, 1.2), y_range=(-1.2, 1.2),
x_axis_location=None, y_axis_location=None, toolbar_location=None,
title="Graph Interaction Demo", background_fill_color="#efefef",
tooltips="index: @index, club: @club")
plot.grid.grid_line_color = None
graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))
graph_renderer.node_renderer.glyph = Scatter(size=15, fill_color="lightblue")
graph_renderer.edge_renderer.glyph = MultiLine(line_color="edge_color",
line_alpha=1, line_width=2)
plot.renderers.append(graph_renderer)
show(plot)