可视化网络图

Bokeh增加了对创建网络图可视化的本地支持,在边和节点之间可以配置交互。

边和节点渲染器

的主要功能 GraphRenderer RER为渲染节点维护单独的边。这允许通过修改GraphRenderer的 node_renderer 财产。可以将默认的圆节点图示符替换为任何XYGlyph实例,例如矩形或椭圆glyph。类似地,可以通过 edge_renderer 财产。边缘图示符当前仅限于多行图示符。

对于属于这些子渲染器的数据源有两个要求:

  • 与节点子呈现器关联的ColumnDataSource必须具有名为 "index" 包含节点的唯一索引。

  • 所需的子列边缘呈现器与两个列关联: "start""end" . 这些列包含边的起点和终点的节点索引。

可以将额外的元数据添加到这些数据源中,以便添加矢量化字形样式或使数据可用于回调或悬停工具提示。

下面是一个代码片段:

  • 将节点图示符替换为椭圆

  • 设置 heightwidth 椭圆的属性作为标量值

  • 设置 fill_color 属性将椭圆作为矢量化字段,并将值添加到节点数据源。

import math
from bokeh.plotting import figure
from bokeh.models import GraphRenderer, Ellipse
from bokeh.palettes import Spectral8

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.glyph = Ellipse(height=0.1, width=0.2, fill_color="fill_color")
graph.node_renderer.data_source.data = dict(
    index=node_indices,
    fill_color=Spectral8)

graph.edge_renderer.data_source.data = dict(
    start=[0]*N,
    end=node_indices)

运行上述代码段不会呈现任何图形,因为我们还没有指定如何在二维空间中排列图形。您可以在下面的部分学习如何做到这一点。

布局提供程序

Bokeh使用单独的 LayoutProvider 为提供笛卡尔空间中的图形坐标而建立的模型。目前,唯一的内置提供程序是 StaticLayoutProvider 字典包含模型的坐标。

此示例将提供程序添加到上面的代码段:

import math

from bokeh.io import output_file, show
from bokeh.models import Ellipse, GraphRenderer, StaticLayoutProvider
from bokeh.palettes import Spectral8
from bokeh.plotting import figure

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)

### start of layout code
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)

plot.renderers.append(graph)

output_file('graph.html')
show(plot)

显式路径

默认情况下, StaticLayoutProvider 将在提供的节点位置之间绘制直线路径。为了提供显式的边路径,还可以提供 edge_renderer bokeh.models.sources.ColumnDataSource . 这个 StaticLayoutProvider 将在 "xs""ys" 数据源的列。请注意,这些路径的顺序应与 "start""end" 积分。还要注意,没有验证它们是否与节点位置匹配,所以在设置显式路径时要格外小心。

此示例扩展了上述示例,以在节点之间绘制二次贝塞尔路径:

import math

from bokeh.io import output_file, show
from bokeh.models import Ellipse, GraphRenderer, StaticLayoutProvider
from bokeh.palettes import Spectral8
from bokeh.plotting import figure

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)

### start of layout code
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)

output_file("graph.html")
show(plot)

Networkx集成

Bokeh支持用networkx集成快速绘制网络图。这个 bokeh.plotting.from_networkx 便利方法接受 networkx.Graph 对象和networkx布局方法,以返回已配置的GraphRender实例。

下面是一个使用 networkx.spring_layout networkx内置“Zachary的空手道俱乐部图”数据集的布局方法:

import networkx as nx

from bokeh.io import output_file, show
from bokeh.plotting import figure, from_networkx

G = nx.karate_club_graph()

plot = figure(title="Networkx Integration Demonstration", x_range=(-1.1,1.1), y_range=(-1.1,1.1),
              tools="", toolbar_location=None)

graph = from_networkx(G, nx.spring_layout, scale=2, center=(0,0))
plot.renderers.append(graph)

output_file("networkx_graph.html")
show(plot)

互动政策

可以通过设置GraphRenderer的 selection_policyinspection_policy 属性。这些策略属性接受一个特殊的 GraphHitTestPolicy 模型实例。

例如,设置 selection_policy=NodesAndLinkedEdges() 将导致选定节点也选择关联的边。同样,设置 inspection_policy=EdgesAndLinkedNodes() 将导致在使用悬停工具悬停边时检查边的起点和终点节点。

用户可能希望自定义 selection_glyphnonselection_glyph 和/或 hover_glyph 边和节点子渲染器的属性,以便将动态可视元素添加到它们的图形交互中。

下面是一个添加了节点和边交互的图形示例:

import networkx as nx

from bokeh.io import output_file, show
from bokeh.models import (BoxSelectTool, Circle, EdgesAndLinkedNodes, HoverTool,
                          MultiLine, NodesAndLinkedEdges, Plot, Range1d, TapTool,)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx

G=nx.karate_club_graph()

plot = Plot(plot_width=400, plot_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))

graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0])
graph_renderer.node_renderer.selection_glyph = Circle(size=15, fill_color=Spectral4[2])
graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color=Spectral4[1])

graph_renderer.edge_renderer.glyph = MultiLine(line_color="#CCCCCC", line_alpha=0.8, line_width=5)
graph_renderer.edge_renderer.selection_glyph = MultiLine(line_color=Spectral4[2], line_width=5)
graph_renderer.edge_renderer.hover_glyph = MultiLine(line_color=Spectral4[1], line_width=5)

graph_renderer.selection_policy = NodesAndLinkedEdges()
graph_renderer.inspection_policy = EdgesAndLinkedNodes()

plot.renderers.append(graph_renderer)

output_file("interactive_graphs.html")
show(plot)

节点和边属性

from_networkx, NetworkX's node/edge attributes are converted for GraphRenderer's node_renderer/edge_renderer .

例如,“Zachary的空手道俱乐部图”数据集有一个名为“Club”的节点属性。可以使用在中转换的节点属性悬停此信息 from_networkx . 类似地,节点/边属性也可以用于颜色信息。

以下是一个图形示例,它悬停节点属性并使用边属性更改颜色:

import networkx as nx

from bokeh.io import output_file, show
from bokeh.models import (BoxZoomTool, Circle, HoverTool,
                          MultiLine, Plot, Range1d, ResetTool,)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx

# Prepare Data
G = nx.karate_club_graph()

SAME_CLUB_COLOR, DIFFERENT_CLUB_COLOR = "black", "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")

# Show with Bokeh
plot = Plot(plot_width=400, plot_height=400,
            x_range=Range1d(-1.1, 1.1), y_range=Range1d(-1.1, 1.1))
plot.title.text = "Graph Interaction Demonstration"

node_hover_tool = HoverTool(tooltips=[("index", "@index"), ("club", "@club")])
plot.add_tools(node_hover_tool, BoxZoomTool(), ResetTool())

graph_renderer = from_networkx(G, nx.spring_layout, scale=1, center=(0, 0))

graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0])
graph_renderer.edge_renderer.glyph = MultiLine(line_color="edge_color", line_alpha=0.8, line_width=1)
plot.renderers.append(graph_renderer)

output_file("interactive_graphs.html")
show(plot)