网络图#

Bokeh 允许您创建网络图可视化并配置边和节点之间的交互。

边和节点渲染器#

GraphRenderer 模型为图节点和边维护单独的子 GlyphRenderers。这允许您通过修改 GraphRenderernode_renderer 属性来自定义节点。您可以用任何 XYGlyph 实例(例如 Rect 或 Ellipse 图形)替换默认的 Circle 节点图形。您也可以通过 edge_renderer 属性类似地修改边的样式属性。要使用边图形,请使用 multi_line 图形方法。

请注意这些子渲染器所属的数据源的以下要求

  • 节点子渲染器的 ColumnDataSource 必须具有一个 "index" 列,其中包含节点的唯一索引。

  • 边子渲染器的 ColumnDataSource 必须具有 "start""end" 列。这些列包含边起点和终点的节点索引。

您可以向这些源添加额外的元数据,以启用矢量化图形样式或使数据可用于回调或悬停工具提示。

以下代码片段

  • 用 Ellipse 替换节点图形,

  • 将标量值分配给 Ellipse 的 heightwidth 属性,

  • 将调色板分配给 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_rendererbokeh.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)

交互策略#

您可以通过设置 GraphRendererselection_policyinspection_policy 属性来配置图的选择或检查行为。这些策略属性接受一个特殊的 GraphHitTestPolicy 模型实例。

例如,将 selection_policy 设置为 NodesAndLinkedEdges() 允许您选择一个节点及其所有关联边。类似地,将 inspection_policy 设置为 EdgesAndLinkedNodes() 允许您通过将鼠标悬停在边上使用 HoverTool 来检查边的 "start""end" 节点。 NodesAndAdjacentNodes() 允许您检查一个节点以及通过图边连接的所有其他节点。

您可以自定义边和节点子渲染器的 selection_glyphnonselection_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_rendereredge_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)